diff --git a/Duplicati/Library/Common/IO/SystemIOWindows.cs b/Duplicati/Library/Common/IO/SystemIOWindows.cs
index 0a1bacde6..423afa317 100644
--- a/Duplicati/Library/Common/IO/SystemIOWindows.cs
+++ b/Duplicati/Library/Common/IO/SystemIOWindows.cs
@@ -1,627 +1,632 @@
-// Copyright (C) 2025, The Duplicati Team
-// https://duplicati.com, hello@duplicati.com
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the "Software"),
-// to deal in the Software without restriction, including without limitation
-// the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and/or sell copies of the Software, and to permit persons to whom the
-// Software is furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-// DEALINGS IN THE SOFTWARE.
-
-using System;
-using System.Collections.Generic;
-using System.Security.AccessControl;
-using System.IO;
-using System.Linq;
-
-using Duplicati.Library.Interface;
-using Newtonsoft.Json;
-using System.Runtime.Versioning;
-using System.Security.Principal;
-
-namespace Duplicati.Library.Common.IO
-{
- [SupportedOSPlatform("windows")]
- public struct SystemIOWindows : ISystemIO
- {
- // Based on the constant names used in
- // https://github.com/dotnet/runtime/blob/v5.0.12/src/libraries/Common/src/System/IO/PathInternal.Windows.cs
- private const string ExtendedDevicePathPrefix = @"\\?\";
- private const string UncPathPrefix = @"\\";
- private const string AltUncPathPrefix = @"//";
- private const string UncExtendedPathPrefix = @"\\?\UNC\";
-
- private static readonly string DIRSEP = Util.DirectorySeparatorString;
-
- ///
- /// The current user name
- ///
- private static readonly string CURRENT_USERNAME = WindowsIdentity.GetCurrent().Name;
-
- ///
- /// The LocalSystem user name
- ///
- private static readonly string LOCAL_SYSTEM_NAME = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null).Translate(typeof(NTAccount)).Value;
-
- ///
- /// Prefix path with one of the extended device path prefixes
- /// (@"\\?\" or @"\\?\UNC\") but only if it's a fully qualified
- /// path with no relative components (i.e., with no "." or ".."
- /// as part of the path).
- ///
- public static string AddExtendedDevicePathPrefix(string path)
- {
- if (IsPrefixedWithExtendedDevicePathPrefix(path))
- {
- // For example: \\?\C:\Temp\foo.txt or \\?\UNC\example.com\share\foo.txt
- return path;
- }
- else
- {
- var hasRelativePathComponents = HasRelativePathComponents(path);
- if (IsPrefixedWithUncPathPrefix(path) && !hasRelativePathComponents)
- {
- // For example: \\example.com\share\foo.txt or //example.com/share/foo.txt
- return UncExtendedPathPrefix + ConvertSlashes(path.Substring(UncPathPrefix.Length));
- }
- else if (DotNetRuntimePathWindows.IsPathFullyQualified(path) && !hasRelativePathComponents)
- {
- // For example: C:\Temp\foo.txt or C:/Temp/foo.txt
- return ExtendedDevicePathPrefix + ConvertSlashes(path);
- }
- else
- {
- // A relative path or a fully qualified path with relative
- // path components so the extended device path prefixes
- // cannot be applied.
- //
- // For example: foo.txt or C:\Temp\..\foo.txt
- return path;
- }
- }
- }
-
- ///
- /// Returns true if prefixed with @"\\" or @"//".
- ///
- private static bool IsPrefixedWithUncPathPrefix(string path)
- {
- return path.StartsWith(UncPathPrefix, StringComparison.Ordinal) ||
- path.StartsWith(AltUncPathPrefix, StringComparison.Ordinal);
- }
-
- ///
- /// Returns true if prefixed with @"\\?\UNC\" or @"\\?\".
- ///
- private static bool IsPrefixedWithExtendedDevicePathPrefix(string path)
- {
- return path.StartsWith(UncExtendedPathPrefix, StringComparison.Ordinal) ||
- path.StartsWith(ExtendedDevicePathPrefix, StringComparison.Ordinal);
- }
-
- private static string[] relativePathComponents = new[] { ".", ".." };
-
- ///
- /// Returns true if contains relative path components; i.e., "." or "..".
- ///
- private static bool HasRelativePathComponents(string path)
- {
- return GetPathComponents(path).Any(pathComponent => relativePathComponents.Contains(pathComponent));
- }
-
- ///
- /// Returns a sequence representing the files and directories in .
- ///
- private static IEnumerable GetPathComponents(string path)
- {
- while (!String.IsNullOrEmpty(path))
- {
- var pathComponent = Path.GetFileName(path);
- if (!String.IsNullOrEmpty(pathComponent))
- {
- yield return pathComponent;
- }
- path = Path.GetDirectoryName(path);
- }
- }
-
- ///
- /// Removes either of the extended device path prefixes
- /// (@"\\?\" or @"\\?\UNC\") if is prefixed
- /// with one of them.
- ///
- public static string RemoveExtendedDevicePathPrefix(string path)
- {
- if (path.StartsWith(UncExtendedPathPrefix, StringComparison.Ordinal))
- {
- // @"\\?\UNC\example.com\share\file.txt" to @"\\example.com\share\file.txt"
- return UncPathPrefix + path.Substring(UncExtendedPathPrefix.Length);
- }
- else if (path.StartsWith(ExtendedDevicePathPrefix, StringComparison.Ordinal))
- {
- // @"\\?\C:\file.txt" to @"C:\file.txt"
- return path.Substring(ExtendedDevicePathPrefix.Length);
- }
- else
- {
- return path;
- }
- }
-
- ///
- /// Convert forward slashes to backslashes.
- ///
- /// Path with forward slashes replaced by backslashes.
- private static string ConvertSlashes(string path)
- {
- return path.Replace("/", Util.DirectorySeparatorString);
- }
-
-
- [SupportedOSPlatform("windows")]
- private class FileSystemAccess
- {
- // Use JsonProperty Attribute to allow readonly fields to be set by deserializer
- // https://github.com/duplicati/duplicati/issues/4028
- [JsonProperty]
- public readonly FileSystemRights Rights;
- [JsonProperty]
- public readonly AccessControlType ControlType;
- [JsonProperty]
- public readonly string SID;
- [JsonProperty]
- public readonly bool Inherited;
- [JsonProperty]
- public readonly InheritanceFlags Inheritance;
- [JsonProperty]
- public readonly PropagationFlags Propagation;
-
- public FileSystemAccess()
- {
- }
-
- public FileSystemAccess(FileSystemAccessRule rule)
- {
- Rights = rule.FileSystemRights;
- ControlType = rule.AccessControlType;
- SID = rule.IdentityReference.Value;
- Inherited = rule.IsInherited;
- Inheritance = rule.InheritanceFlags;
- Propagation = rule.PropagationFlags;
- }
-
- public FileSystemAccessRule Create(FileSystemSecurity owner)
- {
- return (FileSystemAccessRule)owner.AccessRuleFactory(
- new System.Security.Principal.SecurityIdentifier(SID),
- (int)Rights,
- Inherited,
- Inheritance,
- Propagation,
- ControlType);
- }
- }
-
- private static JsonSerializer _cachedSerializer;
-
- private JsonSerializer Serializer
- {
- get
- {
- if (_cachedSerializer != null)
- {
- return _cachedSerializer;
- }
-
- _cachedSerializer = JsonSerializer.Create(
- new JsonSerializerSettings { Culture = System.Globalization.CultureInfo.InvariantCulture });
-
- return _cachedSerializer;
- }
- }
-
- private string SerializeObject(T o)
- {
- using (var tw = new StringWriter())
- {
- Serializer.Serialize(tw, o);
- tw.Flush();
- return tw.ToString();
- }
- }
-
- private T DeserializeObject(string data)
- {
- using (var tr = new StringReader(data))
- return (T)Serializer.Deserialize(tr, typeof(T));
- }
-
-
- [SupportedOSPlatform("windows")]
- private FileSystemSecurity GetAccessControlDir(string path)
- => new DirectoryInfo(AddExtendedDevicePathPrefix(path)).GetAccessControl();
-
-
- [SupportedOSPlatform("windows")]
- private FileSystemSecurity GetAccessControlFile(string path)
- => new FileInfo(AddExtendedDevicePathPrefix(path)).GetAccessControl();
-
-
- [SupportedOSPlatform("windows")]
- private void SetAccessControlFile(string path, FileSecurity rules)
- => new FileInfo(AddExtendedDevicePathPrefix(path)).SetAccessControl(rules);
-
- [SupportedOSPlatform("windows")]
- private void SetAccessControlDir(string path, DirectorySecurity rules)
- => new DirectoryInfo(AddExtendedDevicePathPrefix(path)).SetAccessControl(rules);
-
- #region ISystemIO implementation
- public void DirectoryCreate(string path)
- => Directory.CreateDirectory(AddExtendedDevicePathPrefix(path));
-
- public void DirectoryDelete(string path, bool recursive)
- => Directory.Delete(AddExtendedDevicePathPrefix(path), recursive);
-
- public bool DirectoryExists(string path)
- => Directory.Exists(AddExtendedDevicePathPrefix(path));
-
- public void DirectoryMove(string sourceDirName, string destDirName)
- => Directory.Move(AddExtendedDevicePathPrefix(sourceDirName), AddExtendedDevicePathPrefix(destDirName));
-
- public void FileDelete(string path)
- => File.Delete(AddExtendedDevicePathPrefix(path));
-
- public void FileSetLastWriteTimeUtc(string path, DateTime time)
- => File.SetLastWriteTimeUtc(AddExtendedDevicePathPrefix(path), time);
-
- public void FileSetCreationTimeUtc(string path, DateTime time)
- => File.SetCreationTimeUtc(AddExtendedDevicePathPrefix(path), time);
-
- public DateTime FileGetLastWriteTimeUtc(string path)
- => File.GetLastWriteTimeUtc(AddExtendedDevicePathPrefix(path));
-
- public DateTime FileGetCreationTimeUtc(string path)
- => File.GetCreationTimeUtc(AddExtendedDevicePathPrefix(path));
-
- public bool FileExists(string path)
- => File.Exists(AddExtendedDevicePathPrefix(path));
-
- public FileStream FileOpenRead(string path)
- => File.Open(AddExtendedDevicePathPrefix(path), FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
-
- public FileStream FileOpenReadWrite(string path)
- => !FileExists(path)
- ? FileCreate(path)
- : File.Open(AddExtendedDevicePathPrefix(path), FileMode.Open, FileAccess.ReadWrite);
-
- public FileStream FileOpenWrite(string path)
- => !FileExists(path)
- ? FileCreate(path)
- : File.OpenWrite(AddExtendedDevicePathPrefix(path));
-
- public FileStream FileCreate(string path)
- => File.Create(AddExtendedDevicePathPrefix(path));
-
- public FileAttributes GetFileAttributes(string path)
- => File.GetAttributes(AddExtendedDevicePathPrefix(path));
-
- public void SetFileAttributes(string path, FileAttributes attributes)
- {
- File.SetAttributes(AddExtendedDevicePathPrefix(path), attributes);
- }
-
- ///
- /// Returns the symlink target if the entry is a symlink, and null otherwise
- ///
- /// The file or folder to examine
- /// The symlink target
- public string GetSymlinkTarget(string file)
- => new FileInfo(AddExtendedDevicePathPrefix(file)).LinkTarget;
-
- public IEnumerable EnumerateFileSystemEntries(string path)
- => Directory.EnumerateFileSystemEntries(AddExtendedDevicePathPrefix(path)).Select(RemoveExtendedDevicePathPrefix);
-
- public IEnumerable EnumerateFiles(string path)
- => Directory.EnumerateFiles(AddExtendedDevicePathPrefix(path)).Select(RemoveExtendedDevicePathPrefix);
-
- public IEnumerable EnumerateFiles(string path, string searchPattern, SearchOption searchOption)
- => Directory.EnumerateFiles(AddExtendedDevicePathPrefix(path), searchPattern, searchOption).Select(RemoveExtendedDevicePathPrefix);
-
- public string PathGetFileName(string path)
- => RemoveExtendedDevicePathPrefix(Path.GetFileName(AddExtendedDevicePathPrefix(path)));
-
- public string PathGetDirectoryName(string path)
- => RemoveExtendedDevicePathPrefix(Path.GetDirectoryName(AddExtendedDevicePathPrefix(path)));
-
- public string PathGetExtension(string path)
- => RemoveExtendedDevicePathPrefix(Path.GetExtension(AddExtendedDevicePathPrefix(path)));
-
- public string PathChangeExtension(string path, string extension)
- => RemoveExtendedDevicePathPrefix(Path.ChangeExtension(AddExtendedDevicePathPrefix(path), extension));
-
- public void DirectorySetLastWriteTimeUtc(string path, DateTime time)
- {
- Directory.SetLastWriteTimeUtc(AddExtendedDevicePathPrefix(path), time);
- }
-
- public void DirectorySetCreationTimeUtc(string path, DateTime time)
- {
- Directory.SetCreationTimeUtc(AddExtendedDevicePathPrefix(path), time);
- }
-
- public void FileMove(string source, string target)
- {
- File.Move(AddExtendedDevicePathPrefix(source), AddExtendedDevicePathPrefix(target));
- }
-
- public long FileLength(string path)
- => new FileInfo(AddExtendedDevicePathPrefix(path)).Length;
-
- public string GetPathRoot(string path)
- => IsPrefixedWithExtendedDevicePathPrefix(path)
- ? Path.GetPathRoot(path)
- : RemoveExtendedDevicePathPrefix(Path.GetPathRoot(AddExtendedDevicePathPrefix(path)));
- public string[] GetDirectories(string path)
- => IsPrefixedWithExtendedDevicePathPrefix(path)
- ? Directory.GetDirectories(path)
- : Directory.GetDirectories(AddExtendedDevicePathPrefix(path)).Select(RemoveExtendedDevicePathPrefix).ToArray();
-
- public string[] GetFiles(string path)
- => IsPrefixedWithExtendedDevicePathPrefix(path)
- ? Directory.GetFiles(path)
- : Directory.GetFiles(AddExtendedDevicePathPrefix(path)).Select(RemoveExtendedDevicePathPrefix).ToArray();
-
- public string[] GetFiles(string path, string searchPattern)
- => IsPrefixedWithExtendedDevicePathPrefix(path)
- ? Directory.GetFiles(path, searchPattern)
- : Directory.GetFiles(AddExtendedDevicePathPrefix(path), searchPattern).Select(RemoveExtendedDevicePathPrefix).ToArray();
-
- public DateTime GetCreationTimeUtc(string path)
- => Directory.GetCreationTimeUtc(AddExtendedDevicePathPrefix(path));
-
- public DateTime GetLastWriteTimeUtc(string path)
- => Directory.GetLastWriteTimeUtc(AddExtendedDevicePathPrefix(path));
-
- public IEnumerable EnumerateDirectories(string path)
- => IsPrefixedWithExtendedDevicePathPrefix(path)
- ? Directory.EnumerateDirectories(path)
- : Directory.EnumerateDirectories(AddExtendedDevicePathPrefix(path)).Select(RemoveExtendedDevicePathPrefix);
-
- public IEnumerable EnumerateFileEntries(string path)
- {
- // For consistency with previous implementation, enumerate files first and directories after
- var dir = IsPrefixedWithExtendedDevicePathPrefix(path)
- ? new DirectoryInfo(path)
- : new DirectoryInfo(AddExtendedDevicePathPrefix(path));
-
- foreach (FileInfo file in dir.EnumerateFiles())
- yield return FileEntry(file);
-
- foreach (DirectoryInfo d in dir.EnumerateDirectories())
- yield return DirectoryEntry(d);
- }
-
- public void FileCopy(string source, string target, bool overwrite)
- => File.Copy(AddExtendedDevicePathPrefix(source), AddExtendedDevicePathPrefix(target), overwrite);
-
- public string PathGetFullPath(string path)
- {
- // Desired behavior:
- // 1. If path is already prefixed with \\?\, it should be left untouched
- // 2. If path is not already prefixed with \\?\, the return value should also not be prefixed
- // 3. If path is relative or has relative components, that should be resolved by calling Path.GetFullPath()
- // 4. If path is not relative and has no relative components, prefix with \\?\ to prevent normalization from munging "problematic Windows paths"
- return IsPrefixedWithExtendedDevicePathPrefix(path)
- ? path
- : RemoveExtendedDevicePathPrefix(Path.GetFullPath(AddExtendedDevicePathPrefix(path)));
- }
-
- public IFileEntry DirectoryEntry(string path)
- => DirectoryEntry(new DirectoryInfo(AddExtendedDevicePathPrefix(path)));
-
- public IFileEntry DirectoryEntry(DirectoryInfo dInfo)
- => new FileEntry(dInfo.Name, 0, dInfo.LastAccessTime, dInfo.LastWriteTime)
- {
- IsFolder = true
- };
-
- public IFileEntry FileEntry(string path)
- => FileEntry(new FileInfo(AddExtendedDevicePathPrefix(path)));
-
- public IFileEntry FileEntry(FileInfo fileInfo)
- {
- var lastAccess = new DateTime();
- try
- {
- // Internally this will convert the FILETIME value from Windows API to a
- // DateTime. If the value represents a date after 12/31/9999 it will throw
- // ArgumentOutOfRangeException, because this is not supported by DateTime.
- // Some file systems seem to set strange access timestamps on files, which
- // may lead to this exception being thrown. Since the last accessed
- // timestamp is not important such exeptions are just silently ignored.
- lastAccess = fileInfo.LastAccessTime;
- }
- catch { }
- return new FileEntry(fileInfo.Name, fileInfo.Length, lastAccess, fileInfo.LastWriteTime);
- }
-
- [SupportedOSPlatform("windows")]
- public Dictionary GetMetadata(string path, bool isSymlink, bool followSymlink)
- {
- var isDirTarget = path.EndsWith(DIRSEP, StringComparison.Ordinal);
- var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path;
- var dict = new Dictionary();
-
- FileSystemSecurity rules = isDirTarget ? GetAccessControlDir(targetpath) : GetAccessControlFile(targetpath);
- var objs = new List();
- foreach (var f in rules.GetAccessRules(true, false, typeof(System.Security.Principal.SecurityIdentifier)))
- objs.Add(new FileSystemAccess((FileSystemAccessRule)f));
-
- dict["win-ext:accessrules"] = SerializeObject(objs);
-
- // Only include the following key when its value is True.
- // This prevents unnecessary 'metadata change' detections when upgrading from
- // older versions (pre-2.0.5.101) that didn't store this value at all.
- // When key is not present, its value is presumed False by the restore code.
- if (rules.AreAccessRulesProtected)
- {
- dict["win-ext:accessrulesprotected"] = "True";
- }
-
- return dict;
- }
-
- [SupportedOSPlatform("windows")]
- public void SetMetadata(string path, Dictionary data, bool restorePermissions)
- {
- if (restorePermissions)
- {
- var isDirTarget = path.EndsWith(DIRSEP, StringComparison.Ordinal);
- var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path;
-
- FileSystemSecurity rules = isDirTarget ? GetAccessControlDir(targetpath) : GetAccessControlFile(targetpath);
-
- if (data.ContainsKey("win-ext:accessrulesprotected"))
- {
- bool isProtected = bool.Parse(data["win-ext:accessrulesprotected"]);
- if (rules.AreAccessRulesProtected != isProtected)
- rules.SetAccessRuleProtection(isProtected, false);
- }
-
- if (data.ContainsKey("win-ext:accessrules"))
- {
- var content = DeserializeObject(data["win-ext:accessrules"]);
- var c = rules.GetAccessRules(true, false, typeof(System.Security.Principal.SecurityIdentifier));
- for (var i = c.Count - 1; i >= 0; i--)
- rules.RemoveAccessRule((FileSystemAccessRule)c[i]);
-
- Exception ex = null;
-
- foreach (var r in content)
- {
- // Attempt to apply as many rules as we can
- try
- {
- rules.AddAccessRule(r.Create(rules));
- }
- catch (Exception e)
- {
- ex = e;
- }
- }
-
- if (ex != null)
- throw ex;
- }
-
- if (isDirTarget)
- SetAccessControlDir(targetpath, (DirectorySecurity)rules);
- else
- SetAccessControlFile(targetpath, (FileSecurity)rules);
- }
- }
-
- public string PathCombine(params string[] paths)
- => Path.Combine(paths);
-
- public void CreateSymlink(string symlinkfile, string target, bool asDir)
- {
- if (FileExists(symlinkfile) || DirectoryExists(symlinkfile))
- throw new IOException(string.Format("File already exists: {0}", symlinkfile));
-
-
- if (asDir)
- {
- Directory.CreateSymbolicLink(AddExtendedDevicePathPrefix(symlinkfile), target);
- }
- else
- {
- File.CreateSymbolicLink(AddExtendedDevicePathPrefix(symlinkfile), target);
- }
-
- //Sadly we do not get a notification if the creation fails :(
- FileAttributes attr = 0;
- if ((!asDir && FileExists(symlinkfile)) || (asDir && DirectoryExists(symlinkfile)))
- try { attr = GetFileAttributes(symlinkfile); }
- catch { }
-
- if ((attr & FileAttributes.ReparsePoint) == 0)
- throw new IOException(string.Format("Unable to create symlink, check account permissions: {0}", symlinkfile));
- }
-
- ///
- /// Sets the unix permission user read-write Only.
- ///
- /// The file to set permissions on.
- public void FileSetPermissionUserRWOnly(string path)
- {
- // Create directory security settings
- var security = new FileSecurity();
-
- // Remove inherited permissions to ensure only the current user has access
- security.SetAccessRuleProtection(true, false);
-
- // Grant the current user read access
- security.AddAccessRule(new FileSystemAccessRule(
- CURRENT_USERNAME,
- FileSystemRights.FullControl,
- AccessControlType.Allow
- ));
-
- security.AddAccessRule(new FileSystemAccessRule(
- LOCAL_SYSTEM_NAME,
- FileSystemRights.FullControl,
- AccessControlType.Allow
- ));
-
- // Adjust with the new security settings
- new FileInfo(path).SetAccessControl(security);
- }
-
- ///
- /// Sets the permission to read-write only for the current user.
- ///
- /// The directory to set permissions on.
- public void DirectorySetPermissionUserRWOnly(string path)
- {
- // Create directory security settings
- var security = new DirectorySecurity();
-
- // Remove inherited permissions to ensure only the current user has access
- security.SetAccessRuleProtection(true, false);
-
- // Grant the current user read access
- security.AddAccessRule(new FileSystemAccessRule(
- CURRENT_USERNAME,
- FileSystemRights.FullControl,
- InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, // Apply to subfolders & files
- PropagationFlags.None, // Keeps inheritance settings intact
- AccessControlType.Allow
- ));
-
- security.AddAccessRule(new FileSystemAccessRule(
- LOCAL_SYSTEM_NAME,
- FileSystemRights.FullControl,
- InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, // Apply to subfolders & files
- PropagationFlags.None, // Keeps inheritance settings intact
- AccessControlType.Allow
- ));
-
- // Adjust with the new security settings
- new DirectoryInfo(path).SetAccessControl(security);
- }
- #endregion
- }
-}
-
+// Copyright (C) 2025, The Duplicati Team
+// https://duplicati.com, hello@duplicati.com
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Security.AccessControl;
+using System.IO;
+using System.Linq;
+
+using Duplicati.Library.Interface;
+using Newtonsoft.Json;
+using System.Runtime.Versioning;
+using System.Security.Principal;
+
+namespace Duplicati.Library.Common.IO
+{
+ [SupportedOSPlatform("windows")]
+ public struct SystemIOWindows : ISystemIO
+ {
+ // Based on the constant names used in
+ // https://github.com/dotnet/runtime/blob/v5.0.12/src/libraries/Common/src/System/IO/PathInternal.Windows.cs
+ private const string ExtendedDevicePathPrefix = @"\\?\";
+ private const string UncPathPrefix = @"\\";
+ private const string AltUncPathPrefix = @"//";
+ private const string UncExtendedPathPrefix = @"\\?\UNC\";
+
+ private static readonly string DIRSEP = Util.DirectorySeparatorString;
+
+ ///
+ /// The current user SID
+ ///
+ private static readonly SecurityIdentifier CURRENT_USER_SID = WindowsIdentity.GetCurrent().User;
+
+ ///
+ /// The LocalSystem user SID
+ ///
+ private static readonly SecurityIdentifier LOCAL_SYSTEM_SID = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
+
+ ///
+ /// The Administrator user SID
+ ///
+ private static readonly SecurityIdentifier ADMINISTRATORS_SID = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
+
+ ///
+ /// Prefix path with one of the extended device path prefixes
+ /// (@"\\?\" or @"\\?\UNC\") but only if it's a fully qualified
+ /// path with no relative components (i.e., with no "." or ".."
+ /// as part of the path).
+ ///
+ public static string AddExtendedDevicePathPrefix(string path)
+ {
+ if (IsPrefixedWithExtendedDevicePathPrefix(path))
+ {
+ // For example: \\?\C:\Temp\foo.txt or \\?\UNC\example.com\share\foo.txt
+ return path;
+ }
+ else
+ {
+ var hasRelativePathComponents = HasRelativePathComponents(path);
+ if (IsPrefixedWithUncPathPrefix(path) && !hasRelativePathComponents)
+ {
+ // For example: \\example.com\share\foo.txt or //example.com/share/foo.txt
+ return UncExtendedPathPrefix + ConvertSlashes(path.Substring(UncPathPrefix.Length));
+ }
+ else if (DotNetRuntimePathWindows.IsPathFullyQualified(path) && !hasRelativePathComponents)
+ {
+ // For example: C:\Temp\foo.txt or C:/Temp/foo.txt
+ return ExtendedDevicePathPrefix + ConvertSlashes(path);
+ }
+ else
+ {
+ // A relative path or a fully qualified path with relative
+ // path components so the extended device path prefixes
+ // cannot be applied.
+ //
+ // For example: foo.txt or C:\Temp\..\foo.txt
+ return path;
+ }
+ }
+ }
+
+ ///
+ /// Returns true if prefixed with @"\\" or @"//".
+ ///
+ private static bool IsPrefixedWithUncPathPrefix(string path)
+ {
+ return path.StartsWith(UncPathPrefix, StringComparison.Ordinal) ||
+ path.StartsWith(AltUncPathPrefix, StringComparison.Ordinal);
+ }
+
+ ///
+ /// Returns true if prefixed with @"\\?\UNC\" or @"\\?\".
+ ///
+ private static bool IsPrefixedWithExtendedDevicePathPrefix(string path)
+ {
+ return path.StartsWith(UncExtendedPathPrefix, StringComparison.Ordinal) ||
+ path.StartsWith(ExtendedDevicePathPrefix, StringComparison.Ordinal);
+ }
+
+ private static string[] relativePathComponents = new[] { ".", ".." };
+
+ ///
+ /// Returns true if contains relative path components; i.e., "." or "..".
+ ///
+ private static bool HasRelativePathComponents(string path)
+ {
+ return GetPathComponents(path).Any(pathComponent => relativePathComponents.Contains(pathComponent));
+ }
+
+ ///
+ /// Returns a sequence representing the files and directories in .
+ ///
+ private static IEnumerable GetPathComponents(string path)
+ {
+ while (!String.IsNullOrEmpty(path))
+ {
+ var pathComponent = Path.GetFileName(path);
+ if (!String.IsNullOrEmpty(pathComponent))
+ {
+ yield return pathComponent;
+ }
+ path = Path.GetDirectoryName(path);
+ }
+ }
+
+ ///
+ /// Removes either of the extended device path prefixes
+ /// (@"\\?\" or @"\\?\UNC\") if is prefixed
+ /// with one of them.
+ ///
+ public static string RemoveExtendedDevicePathPrefix(string path)
+ {
+ if (path.StartsWith(UncExtendedPathPrefix, StringComparison.Ordinal))
+ {
+ // @"\\?\UNC\example.com\share\file.txt" to @"\\example.com\share\file.txt"
+ return UncPathPrefix + path.Substring(UncExtendedPathPrefix.Length);
+ }
+ else if (path.StartsWith(ExtendedDevicePathPrefix, StringComparison.Ordinal))
+ {
+ // @"\\?\C:\file.txt" to @"C:\file.txt"
+ return path.Substring(ExtendedDevicePathPrefix.Length);
+ }
+ else
+ {
+ return path;
+ }
+ }
+
+ ///
+ /// Convert forward slashes to backslashes.
+ ///
+ /// Path with forward slashes replaced by backslashes.
+ private static string ConvertSlashes(string path)
+ {
+ return path.Replace("/", Util.DirectorySeparatorString);
+ }
+
+
+ [SupportedOSPlatform("windows")]
+ private class FileSystemAccess
+ {
+ // Use JsonProperty Attribute to allow readonly fields to be set by deserializer
+ // https://github.com/duplicati/duplicati/issues/4028
+ [JsonProperty]
+ public readonly FileSystemRights Rights;
+ [JsonProperty]
+ public readonly AccessControlType ControlType;
+ [JsonProperty]
+ public readonly string SID;
+ [JsonProperty]
+ public readonly bool Inherited;
+ [JsonProperty]
+ public readonly InheritanceFlags Inheritance;
+ [JsonProperty]
+ public readonly PropagationFlags Propagation;
+
+ public FileSystemAccess()
+ {
+ }
+
+ public FileSystemAccess(FileSystemAccessRule rule)
+ {
+ Rights = rule.FileSystemRights;
+ ControlType = rule.AccessControlType;
+ SID = rule.IdentityReference.Value;
+ Inherited = rule.IsInherited;
+ Inheritance = rule.InheritanceFlags;
+ Propagation = rule.PropagationFlags;
+ }
+
+ public FileSystemAccessRule Create(FileSystemSecurity owner)
+ {
+ return (FileSystemAccessRule)owner.AccessRuleFactory(
+ new System.Security.Principal.SecurityIdentifier(SID),
+ (int)Rights,
+ Inherited,
+ Inheritance,
+ Propagation,
+ ControlType);
+ }
+ }
+
+ private static JsonSerializer _cachedSerializer;
+
+ private JsonSerializer Serializer
+ {
+ get
+ {
+ if (_cachedSerializer != null)
+ {
+ return _cachedSerializer;
+ }
+
+ _cachedSerializer = JsonSerializer.Create(
+ new JsonSerializerSettings { Culture = System.Globalization.CultureInfo.InvariantCulture });
+
+ return _cachedSerializer;
+ }
+ }
+
+ private string SerializeObject(T o)
+ {
+ using (var tw = new StringWriter())
+ {
+ Serializer.Serialize(tw, o);
+ tw.Flush();
+ return tw.ToString();
+ }
+ }
+
+ private T DeserializeObject(string data)
+ {
+ using (var tr = new StringReader(data))
+ return (T)Serializer.Deserialize(tr, typeof(T));
+ }
+
+
+ [SupportedOSPlatform("windows")]
+ private FileSystemSecurity GetAccessControlDir(string path)
+ => new DirectoryInfo(AddExtendedDevicePathPrefix(path)).GetAccessControl();
+
+
+ [SupportedOSPlatform("windows")]
+ private FileSystemSecurity GetAccessControlFile(string path)
+ => new FileInfo(AddExtendedDevicePathPrefix(path)).GetAccessControl();
+
+
+ [SupportedOSPlatform("windows")]
+ private void SetAccessControlFile(string path, FileSecurity rules)
+ => new FileInfo(AddExtendedDevicePathPrefix(path)).SetAccessControl(rules);
+
+ [SupportedOSPlatform("windows")]
+ private void SetAccessControlDir(string path, DirectorySecurity rules)
+ => new DirectoryInfo(AddExtendedDevicePathPrefix(path)).SetAccessControl(rules);
+
+ #region ISystemIO implementation
+ public void DirectoryCreate(string path)
+ => Directory.CreateDirectory(AddExtendedDevicePathPrefix(path));
+
+ public void DirectoryDelete(string path, bool recursive)
+ => Directory.Delete(AddExtendedDevicePathPrefix(path), recursive);
+
+ public bool DirectoryExists(string path)
+ => Directory.Exists(AddExtendedDevicePathPrefix(path));
+
+ public void DirectoryMove(string sourceDirName, string destDirName)
+ => Directory.Move(AddExtendedDevicePathPrefix(sourceDirName), AddExtendedDevicePathPrefix(destDirName));
+
+ public void FileDelete(string path)
+ => File.Delete(AddExtendedDevicePathPrefix(path));
+
+ public void FileSetLastWriteTimeUtc(string path, DateTime time)
+ => File.SetLastWriteTimeUtc(AddExtendedDevicePathPrefix(path), time);
+
+ public void FileSetCreationTimeUtc(string path, DateTime time)
+ => File.SetCreationTimeUtc(AddExtendedDevicePathPrefix(path), time);
+
+ public DateTime FileGetLastWriteTimeUtc(string path)
+ => File.GetLastWriteTimeUtc(AddExtendedDevicePathPrefix(path));
+
+ public DateTime FileGetCreationTimeUtc(string path)
+ => File.GetCreationTimeUtc(AddExtendedDevicePathPrefix(path));
+
+ public bool FileExists(string path)
+ => File.Exists(AddExtendedDevicePathPrefix(path));
+
+ public FileStream FileOpenRead(string path)
+ => File.Open(AddExtendedDevicePathPrefix(path), FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+
+ public FileStream FileOpenReadWrite(string path)
+ => !FileExists(path)
+ ? FileCreate(path)
+ : File.Open(AddExtendedDevicePathPrefix(path), FileMode.Open, FileAccess.ReadWrite);
+
+ public FileStream FileOpenWrite(string path)
+ => !FileExists(path)
+ ? FileCreate(path)
+ : File.OpenWrite(AddExtendedDevicePathPrefix(path));
+
+ public FileStream FileCreate(string path)
+ => File.Create(AddExtendedDevicePathPrefix(path));
+
+ public FileAttributes GetFileAttributes(string path)
+ => File.GetAttributes(AddExtendedDevicePathPrefix(path));
+
+ public void SetFileAttributes(string path, FileAttributes attributes)
+ {
+ File.SetAttributes(AddExtendedDevicePathPrefix(path), attributes);
+ }
+
+ ///
+ /// Returns the symlink target if the entry is a symlink, and null otherwise
+ ///
+ /// The file or folder to examine
+ /// The symlink target
+ public string GetSymlinkTarget(string file)
+ => new FileInfo(AddExtendedDevicePathPrefix(file)).LinkTarget;
+
+ public IEnumerable EnumerateFileSystemEntries(string path)
+ => Directory.EnumerateFileSystemEntries(AddExtendedDevicePathPrefix(path)).Select(RemoveExtendedDevicePathPrefix);
+
+ public IEnumerable EnumerateFiles(string path)
+ => Directory.EnumerateFiles(AddExtendedDevicePathPrefix(path)).Select(RemoveExtendedDevicePathPrefix);
+
+ public IEnumerable EnumerateFiles(string path, string searchPattern, SearchOption searchOption)
+ => Directory.EnumerateFiles(AddExtendedDevicePathPrefix(path), searchPattern, searchOption).Select(RemoveExtendedDevicePathPrefix);
+
+ public string PathGetFileName(string path)
+ => RemoveExtendedDevicePathPrefix(Path.GetFileName(AddExtendedDevicePathPrefix(path)));
+
+ public string PathGetDirectoryName(string path)
+ => RemoveExtendedDevicePathPrefix(Path.GetDirectoryName(AddExtendedDevicePathPrefix(path)));
+
+ public string PathGetExtension(string path)
+ => RemoveExtendedDevicePathPrefix(Path.GetExtension(AddExtendedDevicePathPrefix(path)));
+
+ public string PathChangeExtension(string path, string extension)
+ => RemoveExtendedDevicePathPrefix(Path.ChangeExtension(AddExtendedDevicePathPrefix(path), extension));
+
+ public void DirectorySetLastWriteTimeUtc(string path, DateTime time)
+ {
+ Directory.SetLastWriteTimeUtc(AddExtendedDevicePathPrefix(path), time);
+ }
+
+ public void DirectorySetCreationTimeUtc(string path, DateTime time)
+ {
+ Directory.SetCreationTimeUtc(AddExtendedDevicePathPrefix(path), time);
+ }
+
+ public void FileMove(string source, string target)
+ {
+ File.Move(AddExtendedDevicePathPrefix(source), AddExtendedDevicePathPrefix(target));
+ }
+
+ public long FileLength(string path)
+ => new FileInfo(AddExtendedDevicePathPrefix(path)).Length;
+
+ public string GetPathRoot(string path)
+ => IsPrefixedWithExtendedDevicePathPrefix(path)
+ ? Path.GetPathRoot(path)
+ : RemoveExtendedDevicePathPrefix(Path.GetPathRoot(AddExtendedDevicePathPrefix(path)));
+ public string[] GetDirectories(string path)
+ => IsPrefixedWithExtendedDevicePathPrefix(path)
+ ? Directory.GetDirectories(path)
+ : Directory.GetDirectories(AddExtendedDevicePathPrefix(path)).Select(RemoveExtendedDevicePathPrefix).ToArray();
+
+ public string[] GetFiles(string path)
+ => IsPrefixedWithExtendedDevicePathPrefix(path)
+ ? Directory.GetFiles(path)
+ : Directory.GetFiles(AddExtendedDevicePathPrefix(path)).Select(RemoveExtendedDevicePathPrefix).ToArray();
+
+ public string[] GetFiles(string path, string searchPattern)
+ => IsPrefixedWithExtendedDevicePathPrefix(path)
+ ? Directory.GetFiles(path, searchPattern)
+ : Directory.GetFiles(AddExtendedDevicePathPrefix(path), searchPattern).Select(RemoveExtendedDevicePathPrefix).ToArray();
+
+ public DateTime GetCreationTimeUtc(string path)
+ => Directory.GetCreationTimeUtc(AddExtendedDevicePathPrefix(path));
+
+ public DateTime GetLastWriteTimeUtc(string path)
+ => Directory.GetLastWriteTimeUtc(AddExtendedDevicePathPrefix(path));
+
+ public IEnumerable EnumerateDirectories(string path)
+ => IsPrefixedWithExtendedDevicePathPrefix(path)
+ ? Directory.EnumerateDirectories(path)
+ : Directory.EnumerateDirectories(AddExtendedDevicePathPrefix(path)).Select(RemoveExtendedDevicePathPrefix);
+
+ public IEnumerable EnumerateFileEntries(string path)
+ {
+ // For consistency with previous implementation, enumerate files first and directories after
+ var dir = IsPrefixedWithExtendedDevicePathPrefix(path)
+ ? new DirectoryInfo(path)
+ : new DirectoryInfo(AddExtendedDevicePathPrefix(path));
+
+ foreach (FileInfo file in dir.EnumerateFiles())
+ yield return FileEntry(file);
+
+ foreach (DirectoryInfo d in dir.EnumerateDirectories())
+ yield return DirectoryEntry(d);
+ }
+
+ public void FileCopy(string source, string target, bool overwrite)
+ => File.Copy(AddExtendedDevicePathPrefix(source), AddExtendedDevicePathPrefix(target), overwrite);
+
+ public string PathGetFullPath(string path)
+ {
+ // Desired behavior:
+ // 1. If path is already prefixed with \\?\, it should be left untouched
+ // 2. If path is not already prefixed with \\?\, the return value should also not be prefixed
+ // 3. If path is relative or has relative components, that should be resolved by calling Path.GetFullPath()
+ // 4. If path is not relative and has no relative components, prefix with \\?\ to prevent normalization from munging "problematic Windows paths"
+ return IsPrefixedWithExtendedDevicePathPrefix(path)
+ ? path
+ : RemoveExtendedDevicePathPrefix(Path.GetFullPath(AddExtendedDevicePathPrefix(path)));
+ }
+
+ public IFileEntry DirectoryEntry(string path)
+ => DirectoryEntry(new DirectoryInfo(AddExtendedDevicePathPrefix(path)));
+
+ public IFileEntry DirectoryEntry(DirectoryInfo dInfo)
+ => new FileEntry(dInfo.Name, 0, dInfo.LastAccessTime, dInfo.LastWriteTime)
+ {
+ IsFolder = true
+ };
+
+ public IFileEntry FileEntry(string path)
+ => FileEntry(new FileInfo(AddExtendedDevicePathPrefix(path)));
+
+ public IFileEntry FileEntry(FileInfo fileInfo)
+ {
+ var lastAccess = new DateTime();
+ try
+ {
+ // Internally this will convert the FILETIME value from Windows API to a
+ // DateTime. If the value represents a date after 12/31/9999 it will throw
+ // ArgumentOutOfRangeException, because this is not supported by DateTime.
+ // Some file systems seem to set strange access timestamps on files, which
+ // may lead to this exception being thrown. Since the last accessed
+ // timestamp is not important such exeptions are just silently ignored.
+ lastAccess = fileInfo.LastAccessTime;
+ }
+ catch { }
+ return new FileEntry(fileInfo.Name, fileInfo.Length, lastAccess, fileInfo.LastWriteTime);
+ }
+
+ [SupportedOSPlatform("windows")]
+ public Dictionary GetMetadata(string path, bool isSymlink, bool followSymlink)
+ {
+ var isDirTarget = path.EndsWith(DIRSEP, StringComparison.Ordinal);
+ var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path;
+ var dict = new Dictionary();
+
+ FileSystemSecurity rules = isDirTarget ? GetAccessControlDir(targetpath) : GetAccessControlFile(targetpath);
+ var objs = new List();
+ foreach (var f in rules.GetAccessRules(true, false, typeof(System.Security.Principal.SecurityIdentifier)))
+ objs.Add(new FileSystemAccess((FileSystemAccessRule)f));
+
+ dict["win-ext:accessrules"] = SerializeObject(objs);
+
+ // Only include the following key when its value is True.
+ // This prevents unnecessary 'metadata change' detections when upgrading from
+ // older versions (pre-2.0.5.101) that didn't store this value at all.
+ // When key is not present, its value is presumed False by the restore code.
+ if (rules.AreAccessRulesProtected)
+ {
+ dict["win-ext:accessrulesprotected"] = "True";
+ }
+
+ return dict;
+ }
+
+ [SupportedOSPlatform("windows")]
+ public void SetMetadata(string path, Dictionary data, bool restorePermissions)
+ {
+ if (restorePermissions)
+ {
+ var isDirTarget = path.EndsWith(DIRSEP, StringComparison.Ordinal);
+ var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path;
+
+ FileSystemSecurity rules = isDirTarget ? GetAccessControlDir(targetpath) : GetAccessControlFile(targetpath);
+
+ if (data.ContainsKey("win-ext:accessrulesprotected"))
+ {
+ bool isProtected = bool.Parse(data["win-ext:accessrulesprotected"]);
+ if (rules.AreAccessRulesProtected != isProtected)
+ rules.SetAccessRuleProtection(isProtected, false);
+ }
+
+ if (data.ContainsKey("win-ext:accessrules"))
+ {
+ var content = DeserializeObject(data["win-ext:accessrules"]);
+ var c = rules.GetAccessRules(true, false, typeof(System.Security.Principal.SecurityIdentifier));
+ for (var i = c.Count - 1; i >= 0; i--)
+ rules.RemoveAccessRule((FileSystemAccessRule)c[i]);
+
+ Exception ex = null;
+
+ foreach (var r in content)
+ {
+ // Attempt to apply as many rules as we can
+ try
+ {
+ rules.AddAccessRule(r.Create(rules));
+ }
+ catch (Exception e)
+ {
+ ex = e;
+ }
+ }
+
+ if (ex != null)
+ throw ex;
+ }
+
+ if (isDirTarget)
+ SetAccessControlDir(targetpath, (DirectorySecurity)rules);
+ else
+ SetAccessControlFile(targetpath, (FileSecurity)rules);
+ }
+ }
+
+ public string PathCombine(params string[] paths)
+ => Path.Combine(paths);
+
+ public void CreateSymlink(string symlinkfile, string target, bool asDir)
+ {
+ if (FileExists(symlinkfile) || DirectoryExists(symlinkfile))
+ throw new IOException(string.Format("File already exists: {0}", symlinkfile));
+
+
+ if (asDir)
+ {
+ Directory.CreateSymbolicLink(AddExtendedDevicePathPrefix(symlinkfile), target);
+ }
+ else
+ {
+ File.CreateSymbolicLink(AddExtendedDevicePathPrefix(symlinkfile), target);
+ }
+
+ //Sadly we do not get a notification if the creation fails :(
+ FileAttributes attr = 0;
+ if ((!asDir && FileExists(symlinkfile)) || (asDir && DirectoryExists(symlinkfile)))
+ try { attr = GetFileAttributes(symlinkfile); }
+ catch { }
+
+ if ((attr & FileAttributes.ReparsePoint) == 0)
+ throw new IOException(string.Format("Unable to create symlink, check account permissions: {0}", symlinkfile));
+ }
+
+ ///
+ /// Sets the file permissions to user read-write only.
+ ///
+ /// The file to set permissions on.
+ public void FileSetPermissionUserRWOnly(string path)
+ {
+ // Create directory security settings
+ var security = new FileSecurity();
+
+ // Remove inherited permissions to ensure only the current user has access
+ security.SetAccessRuleProtection(true, false);
+
+ var users = new[] {
+ CURRENT_USER_SID,
+ LOCAL_SYSTEM_SID,
+ ADMINISTRATORS_SID
+ }.ToHashSet();
+
+ // Grant the users read access
+ foreach (var user in users)
+ security.AddAccessRule(new FileSystemAccessRule(
+ user,
+ FileSystemRights.FullControl,
+ AccessControlType.Allow
+ ));
+
+ // Adjust with the new security settings
+ new FileInfo(path).SetAccessControl(security);
+ }
+
+ ///
+ /// Sets the directory permissions to read-write only for the current user.
+ ///
+ /// The directory to set permissions on.
+ public void DirectorySetPermissionUserRWOnly(string path)
+ {
+ // Create directory security settings
+ var security = new DirectorySecurity();
+
+ // Remove inherited permissions to ensure only the current user has access
+ security.SetAccessRuleProtection(true, false);
+
+ var users = new[] {
+ CURRENT_USER_SID,
+ LOCAL_SYSTEM_SID,
+ ADMINISTRATORS_SID
+ }.ToHashSet();
+
+ // Grant the users full access
+ foreach (var user in users)
+ security.AddAccessRule(new FileSystemAccessRule(
+ user,
+ FileSystemRights.FullControl, // Using full-control to allow changing permissions as well
+ InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, // Apply to subfolders & files
+ PropagationFlags.None, // Keeps inheritance settings intact
+ AccessControlType.Allow
+ ));
+
+ // Adjust with the new security settings
+ new DirectoryInfo(path).SetAccessControl(security);
+ }
+ #endregion
+ }
+}
+