|
|
|
|
using Alphaleonis.Win32.Filesystem;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Security.AccessControl;
|
|
|
|
|
using System.Security.Principal;
|
|
|
|
|
|
|
|
|
|
namespace Security2
|
|
|
|
|
{
|
|
|
|
|
public partial class FileSystemAccessRule2
|
|
|
|
|
{
|
|
|
|
|
// Rights that FileSystemAccessRule.AccessMaskFromRights rejects, such as the generic rights, which Windows
|
|
|
|
|
// keeps in the inherit-only entries of folders (#17).
|
|
|
|
|
private static bool HasUnsupportedRights(int accessMask)
|
|
|
|
|
{
|
|
|
|
|
return accessMask < 0 || accessMask > (int)FileSystemRights.FullControl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FileSystemSecurity.RemoveAccessRule removes a rule that matches an entry exactly as it is, and otherwise
|
|
|
|
|
// rebuilds it without the Synchronize right, which fails for unsupported rights. For those, do the same
|
|
|
|
|
// without rebuilding the rule.
|
|
|
|
|
private static void RemoveRule(FileSystemSecurity sd, FileSystemAccessRule ace, bool removeSpecific)
|
|
|
|
|
{
|
|
|
|
|
var accessMask = (int)ace.FileSystemRights;
|
|
|
|
|
|
|
|
|
|
if (!HasUnsupportedRights(accessMask))
|
|
|
|
|
{
|
|
|
|
|
if (removeSpecific)
|
|
|
|
|
sd.RemoveAccessRuleSpecific(ace);
|
|
|
|
|
else
|
|
|
|
|
sd.RemoveAccessRule(ace);
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var sid = (SecurityIdentifier)ace.IdentityReference.Translate(typeof(SecurityIdentifier));
|
|
|
|
|
var exactMatch = sd.GetAccessRules(true, true, typeof(SecurityIdentifier))
|
|
|
|
|
.OfType<FileSystemAccessRule>()
|
|
|
|
|
.Any(rule => (int)rule.FileSystemRights == accessMask &&
|
|
|
|
|
rule.IdentityReference == sid &&
|
|
|
|
|
rule.AccessControlType == ace.AccessControlType);
|
|
|
|
|
|
|
|
|
|
var ruleToRemove = exactMatch ? ace : (FileSystemAccessRule)sd.AccessRuleFactory(sid,
|
|
|
|
|
accessMask & ~(int)FileSystemRights.Synchronize, false, ace.InheritanceFlags, ace.PropagationFlags, ace.AccessControlType);
|
|
|
|
|
|
|
|
|
|
// Like RemoveAccessRule, ignore whether an entry was changed: removing an entry that doesn't exist is not
|
|
|
|
|
// an error.
|
|
|
|
|
bool modified;
|
|
|
|
|
sd.ModifyAccessRule(removeSpecific ? AccessControlModification.RemoveSpecific : AccessControlModification.Remove, ruleToRemove, out modified);
|
|
|
|
|
}
|
|
|
|
|
public static void RemoveFileSystemAccessRule(FileSystemInfo item, IdentityReference2 account, FileSystemRights2 rights, AccessControlType type, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, bool removeSpecific = false)
|
|
|
|
|
{
|
|
|
|
|
if (type == AccessControlType.Allow)
|
|
|
|
|
rights = rights | FileSystemRights2.Synchronize;
|
|
|
|
|
|
|
|
|
|
FileSystemAccessRule ace = null;
|
|
|
|
|
|
|
|
|
|
if (item as FileInfo != null)
|
|
|
|
|
{
|
|
|
|
|
var file = (FileInfo)item;
|
|
|
|
|
var sd = file.GetAccessControl(AccessControlSections.Access);
|
|
|
|
|
|
|
|
|
|
ace = (FileSystemAccessRule)sd.AccessRuleFactory(account, (int)rights, false, inheritanceFlags, propagationFlags, type);
|
|
|
|
|
RemoveRule(sd, ace, removeSpecific);
|
|
|
|
|
|
fix: write only the sections of the security descriptor that a cmdlet changes
The access and audit cmdlets wrote the owner of an item back with the
entries they changed. For a DACL without the auto-inherit flag, Windows
returns the owner and the group even when only the DACL is read, and the
cmdlets wrote every section that the descriptor held. Without the Restore
privilege, or on a file server that refuses the owner, the write failed
with error 1307 (#34).
- Add-NTFSAccess, Clear-NTFSAccess, Add-NTFSAudit, and Clear-NTFSAudit
read only the DACL or the SACL. FileSystemSecurity2.Write(),
Remove-NTFSAccess, and Remove-NTFSAudit write only the sections they
read, which also fixes the access inheritance cmdlets.
- Read together with the SACL, the inherited entries of such a DACL lose
their inherited flag when the parent folder has no SACL, and the
cmdlets stored them as explicit copies. Get-NTFSSecurityDescriptor now
reads the DACL in a separate call.
- Set-NTFSSecurityDescriptor writes only the sections that changed since
they were read; an unchanged descriptor writes nothing (maintainer
decision of 2026-10-06).
- Clear-NTFSAudit writes nothing for an item without a SACL, and reports
an error without the Security privilege (maintainer decision).
The regression tests failed before and pass after the fix in Windows
PowerShell 5.1 and PowerShell 7, elevated and as a basic user.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
4 weeks ago
|
|
|
// Only the DACL: Windows can return the owner with it, and writing that back fails for an owner that the
|
|
|
|
|
// user cannot assign (#34).
|
|
|
|
|
file.SetAccessControl(sd, AccessControlSections.Access);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
DirectoryInfo directory = (DirectoryInfo)item;
|
|
|
|
|
|
|
|
|
|
var sd = directory.GetAccessControl(AccessControlSections.Access);
|
|
|
|
|
|
|
|
|
|
ace = (FileSystemAccessRule)sd.AccessRuleFactory(account, (int)rights, false, inheritanceFlags, propagationFlags, type);
|
|
|
|
|
RemoveRule(sd, ace, removeSpecific);
|
|
|
|
|
|
fix: write only the sections of the security descriptor that a cmdlet changes
The access and audit cmdlets wrote the owner of an item back with the
entries they changed. For a DACL without the auto-inherit flag, Windows
returns the owner and the group even when only the DACL is read, and the
cmdlets wrote every section that the descriptor held. Without the Restore
privilege, or on a file server that refuses the owner, the write failed
with error 1307 (#34).
- Add-NTFSAccess, Clear-NTFSAccess, Add-NTFSAudit, and Clear-NTFSAudit
read only the DACL or the SACL. FileSystemSecurity2.Write(),
Remove-NTFSAccess, and Remove-NTFSAudit write only the sections they
read, which also fixes the access inheritance cmdlets.
- Read together with the SACL, the inherited entries of such a DACL lose
their inherited flag when the parent folder has no SACL, and the
cmdlets stored them as explicit copies. Get-NTFSSecurityDescriptor now
reads the DACL in a separate call.
- Set-NTFSSecurityDescriptor writes only the sections that changed since
they were read; an unchanged descriptor writes nothing (maintainer
decision of 2026-10-06).
- Clear-NTFSAudit writes nothing for an item without a SACL, and reports
an error without the Security privilege (maintainer decision).
The regression tests failed before and pass after the fix in Windows
PowerShell 5.1 and PowerShell 7, elevated and as a basic user.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
4 weeks ago
|
|
|
directory.SetAccessControl(sd, AccessControlSections.Access);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void RemoveFileSystemAccessRule(FileSystemInfo item, List<IdentityReference2> accounts, FileSystemRights2 rights, AccessControlType type, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, bool removeSpecific = false)
|
|
|
|
|
{
|
|
|
|
|
foreach (var account in accounts)
|
|
|
|
|
{
|
|
|
|
|
RemoveFileSystemAccessRule(item, account, rights, type, inheritanceFlags, propagationFlags, removeSpecific);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void RemoveFileSystemAccessRule(string path, IdentityReference2 account, FileSystemRights2 rights, AccessControlType type, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, bool removeSpecific = false)
|
|
|
|
|
{
|
|
|
|
|
if (File.Exists(path))
|
|
|
|
|
{
|
|
|
|
|
var item = new FileInfo(path);
|
|
|
|
|
RemoveFileSystemAccessRule(item, account, rights, type, inheritanceFlags, propagationFlags, removeSpecific);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var item = new DirectoryInfo(path);
|
|
|
|
|
RemoveFileSystemAccessRule(item, account, rights, type, inheritanceFlags, propagationFlags, removeSpecific);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void RemoveFileSystemAccessRule(string path, List<IdentityReference2> account, FileSystemRights2 rights, AccessControlType type, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, bool removeSpecific = false)
|
|
|
|
|
{
|
|
|
|
|
if (File.Exists(path))
|
|
|
|
|
{
|
|
|
|
|
var item = new FileInfo(path);
|
|
|
|
|
RemoveFileSystemAccessRule(item, account, rights, type, inheritanceFlags, propagationFlags, removeSpecific);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var item = new DirectoryInfo(path);
|
|
|
|
|
RemoveFileSystemAccessRule(item, account, rights, type, inheritanceFlags, propagationFlags, removeSpecific);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void RemoveFileSystemAccessRule(FileSystemInfo item, FileSystemAccessRule ace, bool removeSpecific = false)
|
|
|
|
|
{
|
|
|
|
|
if (item as FileInfo != null)
|
|
|
|
|
{
|
|
|
|
|
var file = (FileInfo)item;
|
|
|
|
|
var sd = file.GetAccessControl(AccessControlSections.Access);
|
|
|
|
|
|
|
|
|
|
RemoveRule(sd, ace, removeSpecific);
|
|
|
|
|
|
fix: write only the sections of the security descriptor that a cmdlet changes
The access and audit cmdlets wrote the owner of an item back with the
entries they changed. For a DACL without the auto-inherit flag, Windows
returns the owner and the group even when only the DACL is read, and the
cmdlets wrote every section that the descriptor held. Without the Restore
privilege, or on a file server that refuses the owner, the write failed
with error 1307 (#34).
- Add-NTFSAccess, Clear-NTFSAccess, Add-NTFSAudit, and Clear-NTFSAudit
read only the DACL or the SACL. FileSystemSecurity2.Write(),
Remove-NTFSAccess, and Remove-NTFSAudit write only the sections they
read, which also fixes the access inheritance cmdlets.
- Read together with the SACL, the inherited entries of such a DACL lose
their inherited flag when the parent folder has no SACL, and the
cmdlets stored them as explicit copies. Get-NTFSSecurityDescriptor now
reads the DACL in a separate call.
- Set-NTFSSecurityDescriptor writes only the sections that changed since
they were read; an unchanged descriptor writes nothing (maintainer
decision of 2026-10-06).
- Clear-NTFSAudit writes nothing for an item without a SACL, and reports
an error without the Security privilege (maintainer decision).
The regression tests failed before and pass after the fix in Windows
PowerShell 5.1 and PowerShell 7, elevated and as a basic user.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
4 weeks ago
|
|
|
file.SetAccessControl(sd, AccessControlSections.Access);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
DirectoryInfo directory = (DirectoryInfo)item;
|
|
|
|
|
|
|
|
|
|
var sd = directory.GetAccessControl(AccessControlSections.Access);
|
|
|
|
|
|
|
|
|
|
RemoveRule(sd, ace, removeSpecific);
|
|
|
|
|
|
fix: write only the sections of the security descriptor that a cmdlet changes
The access and audit cmdlets wrote the owner of an item back with the
entries they changed. For a DACL without the auto-inherit flag, Windows
returns the owner and the group even when only the DACL is read, and the
cmdlets wrote every section that the descriptor held. Without the Restore
privilege, or on a file server that refuses the owner, the write failed
with error 1307 (#34).
- Add-NTFSAccess, Clear-NTFSAccess, Add-NTFSAudit, and Clear-NTFSAudit
read only the DACL or the SACL. FileSystemSecurity2.Write(),
Remove-NTFSAccess, and Remove-NTFSAudit write only the sections they
read, which also fixes the access inheritance cmdlets.
- Read together with the SACL, the inherited entries of such a DACL lose
their inherited flag when the parent folder has no SACL, and the
cmdlets stored them as explicit copies. Get-NTFSSecurityDescriptor now
reads the DACL in a separate call.
- Set-NTFSSecurityDescriptor writes only the sections that changed since
they were read; an unchanged descriptor writes nothing (maintainer
decision of 2026-10-06).
- Clear-NTFSAudit writes nothing for an item without a SACL, and reports
an error without the Security privilege (maintainer decision).
The regression tests failed before and pass after the fix in Windows
PowerShell 5.1 and PowerShell 7, elevated and as a basic user.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
4 weeks ago
|
|
|
directory.SetAccessControl(sd, AccessControlSections.Access);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static FileSystemAccessRule2 RemoveFileSystemAccessRule(FileSystemSecurity2 sd, IdentityReference2 account, FileSystemRights2 rights, AccessControlType type, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, bool removeSpecific = false)
|
|
|
|
|
{
|
|
|
|
|
if (type == AccessControlType.Allow)
|
|
|
|
|
rights = rights | FileSystemRights2.Synchronize;
|
|
|
|
|
|
|
|
|
|
var ace = (FileSystemAccessRule)sd.SecurityDescriptor.AccessRuleFactory(account, (int)rights, false, inheritanceFlags, propagationFlags, type);
|
|
|
|
|
RemoveRule(sd.SecurityDescriptor, ace, removeSpecific);
|
|
|
|
|
|
|
|
|
|
return ace;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static IEnumerable<FileSystemAccessRule2> RemoveFileSystemAccessRule(FileSystemSecurity2 sd, List<IdentityReference2> accounts, FileSystemRights2 rights, AccessControlType type, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, bool removeSpecific = false)
|
|
|
|
|
{
|
|
|
|
|
var aces = new List<FileSystemAccessRule2>();
|
|
|
|
|
|
|
|
|
|
foreach (var account in accounts)
|
|
|
|
|
{
|
|
|
|
|
aces.Add(RemoveFileSystemAccessRule(sd, account, rights, type, inheritanceFlags, propagationFlags, removeSpecific));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return aces;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|