Managing permissions on files and folders is essential to maintaining data security and access control in a Windows environment. With PowerShell, administrators have powerful tools to manage these permissions efficiently.
Two fundamental cmdlets for working with permissions are Get-Acl and Set-Acl. In this blog post, weโll explore using these cmdlets to manage NTFS permissions using PowerShell effectively.
Note. The Get-Acl and Set-Acl cmdlets can work with several PowerShell providers, including FileSystem, Registry, and Certificate. This article focuses only on FileSystem examples.
Requirements
- OS: Windows 11 or Windows Server 2022/2025.
- PowerShell: Windows PowerShell 5.1/PowerShell 7.6 LTS.
- Permissions: Read access to the target object’s security descriptor; admin privileges/additional rights may be required to modify ownership, ACLs, or audit settings.
- The examples use the built-in Microsoft.PowerShell.Security module and the Windows file system security APIs.
Understanding Access Control List (ACL) and Access Control Entry (ACE)
Access Control List (ACL)
An Access Control List (ACL) defines the permissions associated with a file/folder. Its Discretionary ACL (DACL) contains Access Control Entries (ACEs) that allow or deny specific rights to users and groups. Windows evaluates these entries against the security token of the account requesting access.
Important. Note that Set-Acl applies the supplied security descriptor to the target file/folder. Don’t forget to review the existing ACL before making changes in order to avoid unintentionally changing permissions/other security settings.
Access Control Entry (ACE)
An ACE, or Access Control Entry, is a component of an ACL that defines the permissions granted or denied to a specific security principal (user, group, or other entity). An ACE contains the following elements:
- Security Principal: The user or group for whom the permissions are being defined. It can also represent specific types of users, such as anonymous users or built-in system accounts.
- Access Type: This specifies whether the ACE allows or denies the specified permissions. Access types can include โAllowโ or โDeny.โ
- Access Mask (Rights): The access mask is a set of flags that represent specific permissions or rights. It defines what actions the security principal is allowed or denied on the resource. Examples of access mask flags include read, write, execute, delete, modify, and more.
- Inheritance Flags: Inherited ACEs allow permissions to be propagated from a parent object to its child objects. Inheritance flags indicate whether the ACE should be inherited and how it should be inherited.
- Propagation Flags: These flags modify how inherited ACEs are propagated. NoPropagateInherit prevents an ACE from being propagated beyond the first generation of child objects, while InheritOnly prevents the ACE from applying to the object itself and limits it to inheritance by child objects.
When a user or group attempts to access a resource, the operating system checks the ACL associated with that resource to determine whether the requested action is allowed or denied based on the ACEs within the ACL.
When Windows evaluates an ACL, it does not simply read permissions as a flat list. Instead, access is determined by a strict evaluation model. Note that access decisions are based on the userโs access token, which contains SIDs from direct and nested group memberships. Keep in mind that ACLs are evaluated against this token, not against the user identity directly.
- Explicit Deny and Allow rules are evaluated according to the ACL evaluation order. In a canonical DACL, explicit ACEs precede inherited ACEs, and Deny ACEs precede Allow ACEs within each group. Access checks also depend on the requested rights and the user’s access token, so you should not treat this ordering as a complete effective-access calculation.
- Explicit rules override inherited rules. Explicit ACEs are evaluated before inherited ACEs.
This means local object permissions are stronger than parent directory permissions. - Evaluation is order-sensitive inside ACL. Windows evaluates ACEs in order until access is granted/access is explicitly denied.
- Group membership expands evaluation scope. A user can receive Allow and Deny permissions through multiple group memberships. A Deny ACE blocks only the rights it specifies when that ACE applies during the access check; the outcome depends on the requested rights, ACE order, and the user’s access token.
- Effective permissions are computed from the access token, not from ACL entries directly.
Windows expands group membership (including nested groups) before evaluating ACLs.
When modifying ACLs, you should always consider existing Deny rules. Keep in mind that adding an Allow ACE does not cancel a conflicting Deny ACE. The resulting access depends on the rights specified in each ACE and the order in which applicable ACEs are evaluated.
Understanding Get-Acl and Set-Acl
PowerShell version differences in ACL handling
Get-Acl and Set-Acl work in both Windows PowerShell 5.1 and PowerShell 7. For NTFS permissions on Windows, behavior is generally identical when executed locally. Differences may appear when ACL objects are serialized through remoting/automation pipelines.
Get-Acl
The Get-Acl cmdlet retrieves the ACL information for a specified file or folder object. The object can be at a local or network location. The ACL contains a list of ACEs that define the permissions and security principals associated with the object. To use Get-Acl, the syntax is as follows.
Get-Acl -LiteralPath C:\Path\To\FileOrFolder
For example, letโs get the ACL of a file and folder on the local machine:
# Folder ACL
$dir_acl = Get-Acl -LiteralPath C:\demo\dir1
$dir_acl | Get-Member -MemberType CodeProperty, Property
# File ACL
$file_acl = Get-Acl -LiteralPath C:\demo\dir1\dummy_file.txt
$file_acl | Get-Member -MemberType CodeProperty, Property
The folder ACL and file ACL returns these two object classes, respectively.
[System.Security.AccessControl.DirectorySecurity]

(link to learn more)
[System.Security.AccessControl.FileSecurity]

(link to learn more)
Youโll notice that both ACL objects have identical properties, indicating that they are only different in the item type (directory vs. file.)
Once you have retrieved the objectโs ACL, you can view the properties by piping the object to the Format-List cmdlet.
$dir_acl | Format-list $file_acl | Format-List
For comparison, the ACL object shown in PowerShell and File Explorer is below.

The brief description of the output is as follows.
- Path = Path to the resource (Provider::Resource Path)
- Owner = Owner of the resource
- Group = Group of the owner
- Access = Access control entries
Note. The -536805376 value is a numeric representation of a file system access-rights mask associated with Modify permissions. Its appearance in the ACL output does not, by itself, indicate if inheritance is enabled on the directory. You should check $acl.AreAccessRulesProtected in order to determine if inheritance is disabled on the object.
Set-Acl
The Set-Acl cmdlet applies a security descriptor to a file/folder. To add, modify, or remove ACEs, first you need to update the ACL object in memory using methods such as AddAccessRule() or RemoveAccessRuleSpecific(), then apply the modified descriptor with Set-Acl. The basic syntax is:
Set-Acl -LiteralPath C:\Path\To\FileOrFolder -AclObject $aclObject
You can retrieve the $aclObject from another file/folder or create it from scratch. However, applying a security descriptor copied from another object can also change its owner, access rules, and inheritance settings. Before using Set-Acl, you need to review the descriptor and ensure that its security settings are appropriate for the target object. In case you intend to change only specific permissions, you need to modify the target object’s existing ACL rather than copying the entire descriptor from another object. The following sections demonstrate common ACL operations.
Managing Permissions with Get-Acl and Set-Acl
Modifying permissions on an object is a common task in file management. Combining Get-Acl and Set-Acl makes a perfect pair when modifying existing permissions.
Suppose you want to replace the owner of a directory and grant โReadโ and โWriteโ permissions to a group.
- Directory object path: C:\demo\dir1
- New owner: THEITBROS\alpha
- Additional group: THEITBROS\CA IT Ops
Note that the behavior of ACL cmdlets is consistent across PowerShell 5.1 and 7 when executed locally on Windows, but you can see differences in remoting and automation contexts.
Changing the Owner
Before modifying permissions, retrieving the current ACL using Get-Acl is a good practice.
$acl = Get-Acl -LiteralPath C:\demo\dir1
Important. Keep in mind that changing a file/folder owner requires appropriate permissions and privileges. SeTakeOwnershipPrivilege allows an account to take ownership of an object, typically by assigning ownership to itself. Assigning ownership to another user, as in our example, may require SeRestorePrivilege. Elevation alone does not guarantee that the operation will succeed. The ownership change is applied to the file or folder when Set-Acl is called, and that operation can fail with an UnauthorizedAccessException in case the required access rights/privileges are unavailable.
You can check the current owner before making any ownership changes with the command:
(Get-Acl -LiteralPath C:\demo\dir1).Owner
The current ACL is now stored in the $acl variable. Letโs manipulate this variable to set the new owner and add the access control entry for the group.
To set the owner, call the SetOwner() method.
# Specify the new owner $newOwner = 'THEITBROS\alpha' # Set the new owner $newOwnerId = ([System.Security.Principal.NTAccount]::new($newOwner)).Translate([System.Security.Principal.SecurityIdentifier]) $acl.SetOwner($newOwnerId)
The owner has now been changed in the in-memory ACL object.

Hint. You can also change a file or folder ownership using the takeown.exe command.
Adding Access Control Entry
To add a Read and Write access to the group, we must create the object [System.Security.AccessControl.FileSystemAccessRule] (learn more here).
# Specify the new group to add
$addtlGroupIdentity = 'THEITBROS\CA IT Ops'
# Define the access rights
$addtlGroupAccessRights = 'Read, Write'
# Define the access type
$addtlGroupAccessType = 'Allow'
# Inherit the ACE to child folders and files
$inheritanceFlags = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor `
[System.Security.AccessControl.InheritanceFlags]::ObjectInherit
$propagationFlags = [System.Security.AccessControl.PropagationFlags]::None
Because the example applies permissions to a directory, the inheritance flags ensure that the ACE is also inherited by child folders and files.
$newACE = [System.Security.AccessControl.FileSystemAccessRule]::new(
$addtlGroupIdentity,
$addtlGroupAccessRights,
$inheritanceFlags,
$propagationFlags,
$addtlGroupAccessType
)

Add the new ACE to the ACL object by calling the AddAccessRule() method.
# Set the new ACE $acl.AddAccessRule($newACE) # Display the updated ACE $acl.Access | Format-Table
Note that adding an Allow ACE does not guarantee access. The result depends on the applicable ACEs, their order in the DACL, and the requested rights. An explicit Allow ACE can be evaluated before an inherited Deny ACE, but a Deny ACE that is evaluated first can prevent access to the rights it denies.

Apply the updated ACL:
# Update the directory ACL
Set-Acl -LiteralPath C:\demo\dir1 -AclObject $acl
# Get the new directory ACL
Get-Acl -LiteralPath C:\demo\dir1 | Format-List
Removing Access Control Entry
Suppose the user or group is no longer eligible to access a folder, and you must remove its ACE from that directoryโs ACL. Hereโs how to do it.
Get the current ACL and list the ACEs.
$acl = Get-Acl -LiteralPath C:\demo\dir1
$acl.Access | Format-Table

Note. You should avoid removing an ACE by its position in the Access list. The order of entries can change, and an index may refer to a different ACE after the ACL is modified. Instead, you need to identify the ACE by its security principal, access rights, access type, and inheritance settings, as shown below.
To remove that access control entry, the command to run is:
$path = 'C:\demo\dir1'
$identity = 'THEITBROS\CA IT Ops'
$acl = Get-Acl -LiteralPath $path
# Resolve the group name to a SID for reliable identity matching.
$identitySid = (
[System.Security.Principal.NTAccount]::new($identity)
).Translate(
[System.Security.Principal.SecurityIdentifier]
).Value
# Specify the exact ACE to remove.
$expectedRights = (
[System.Security.AccessControl.FileSystemRights]::Read -bor
[System.Security.AccessControl.FileSystemRights]::Write
)
$expectedInheritance = (
[System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor
[System.Security.AccessControl.InheritanceFlags]::ObjectInherit
)
$expectedPropagation = [System.Security.AccessControl.PropagationFlags]::None
$candidates = @(
$acl.Access | Where-Object {
try {
$ruleSid = if ($_.IdentityReference -is
[System.Security.Principal.SecurityIdentifier]) {
$_.IdentityReference.Value
}
else {
$_.IdentityReference.Translate(
[System.Security.Principal.SecurityIdentifier]
).Value
}
}
catch {
throw "Failed to resolve ACE identity '$($_.IdentityReference)' to a SID. No changes applied. $($_.Exception.Message)"
}
$ruleSid -eq $identitySid -and
$_.AccessControlType -eq
[System.Security.AccessControl.AccessControlType]::Allow -and
$_.FileSystemRights -eq $expectedRights -and
$_.InheritanceFlags -eq $expectedInheritance -and
$_.PropagationFlags -eq $expectedPropagation -and
-not $_.IsInherited
}
)
if ($candidates.Count -ne 1) {
throw "Expected exactly one matching explicit ACE; found $($candidates.Count). No changes made."
}
$acl.RemoveAccessRuleSpecific($candidates[0])
Set-Acl -LiteralPath $path -AclObject $acl -ErrorAction Stop
# Verify the remaining rules.
(Get-Acl -LiteralPath $path).Access |
Format-Table IdentityReference, FileSystemRights,
AccessControlType, IsInherited, InheritanceFlags
RemoveAccessRule() can remove/modify matching explicit access permissions and is not intended to guarantee removal of one exact ACE. You can use RemoveAccessRuleSpecific() when you need to remove a specific matching rule. In case multiple rules exist for the same identity, you need to match the rights and inheritance settings as well as the identity.
Neither method removes inherited ACEs. In case you want to change inherited permissions, modify the relevant ACE on the parent object/deliberately change inheritance on the child.
In case you need to remove all explicit access rules for a specific identity from an object’s DACL, you should use PurgeAccessRules() instead of matching a single ACE. This is a broader operation and you should use it only when removing every explicit rule for that identity is intentional.
$path = 'C:\demo\dir1'
$identity = 'THEITBROS\CA IT Ops'
$acl = Get-Acl -LiteralPath $path
$sid = ([System.Security.Principal.NTAccount]::new($identity)).
Translate([System.Security.Principal.SecurityIdentifier])
$acl.PurgeAccessRules($sid)
Set-Acl -LiteralPath $path -AclObject $acl -ErrorAction Stop
Note that disabling inheritance can have significant security consequences and you should not use it as a shortcut for removing a single inherited permission.
In enterprise environments, ACL modifications should always be identity-based, not index-based. You should always match on IdentityReference and Rights, not array position.
FileSystemRights is a bitmask. You can use exact equality (-eq) when you need to match the complete rights mask. You should use a bitwise check (-band) when you need to determine if an ACE includes specific rights, even if it grants additional permissions. Our removal script above uses exact equality, so ACEs with additional rights will not match.
As you can see, the ACE for โTHEITBROS\CA IT Opsโ has been removed.

Keep in mind that removing an ACE can change effective permissions in unexpected ways (especially when multiple Allow and Deny rules exist for the same identity/group membership chain).
icacls.exe can be useful for bulk removal of explicit permissions. However, its removal options are broader than removing a single matching ACE with RemoveAccessRuleSpecific(). Don’t forget to review the target object’s ACL before running the command.
icacls C:\demo\dir1 /remove:g "THEITBROS\CA IT Ops"The /remove:g option removes explicit Allow ACEs for the specified principal, while /remove:d removes explicit Deny ACEs. Note that these commands are not equivalent to RemoveAccessRuleSpecific(), which removes a specific matching ACE.
Turning-Off Inheritance Without Breaking Existing Access Rights
Turning off inheritance without breaking existing access rights involves removing inheritance from an objectโs ACL while preserving the existing access rights. This can be useful when you want to establish custom permissions for an object without affecting the permissions inherited from its parent. Hereโs how you can achieve this using PowerShell.
First, we retrieve the current ACL of the target object.
# Define the path to the file or folder
$objectPath = "C:\demo\dir1"
# Retrieve the current ACL
$acl = Get-Acl -LiteralPath $objectPath
# Show the current inheritance status
$acl.AreAccessRulesProtected
In this example, we can confirm that the inheritance is turned on.

To remove the inheritance, weโll call the SetAccessRuleProtection(bool isProtected, bool preserveInheritance) method. This method requires two arguments:
- isProtected (Boolean) โ Specifies if inheritance is disabled ($true) or enabled ($false).
- preserveInheritance (Boolean) โ specifies if inherited ACEs are preserved as explicit ACEs ($true) or removed ($false) when inheritance is disabled.
# Disable inheritance while preserving existing ACEs $acl.SetAccessRuleProtection($true, $true) # Disable inheritance, keep existing ACEs
Note that preserving inherited ACEs converts them into explicit ACEs. These permissions will no longer automatically reflect changes to the parent folder’s ACL, which can lead to permission drift over time.
Finally, apply the modified ACL to the object:
Set-ACL -LiteralPath $objectPath -AclObject $acl

NTFS vs Share Permissions Interaction
NTFS permissions alone do not define effective access in network cases. When accessing files over SMB shares, both Share permissions and NTFS permissions are evaluated.
- Effective access rule:
Most restrictive of Share permissions and NTFS permissions.
Here is an example:
Share: Full Control
NTFS: Read only
Result: Read only
Share: Read
NTFS: Full Control
Result: Read only
Note that share permissions are evaluated only at SMB layer. Local access (console/RDP) ignores share ACLs entirely.
Important note. You should note that Set-Acl modifies only NTFS permissions. It does not affect Share permissions.
Auditing NTFS Access and Permission Changes
Windows uses System Access Control Lists (SACLs) in order to define which file system access attempts should be audited. Unlike DACLs, which control access permissions, SACLs specify which events Windows should record.
To enable NTFS auditing, you need to config the Audit File System policy under Advanced Audit Policy Configuration in Group Policy and define the required audit rules on the target file/folder.
You can inspect existing audit rules with PowerShell:
$path = 'C:\demo\dir1'
(Get-Acl -LiteralPath $path -Audit).Audit |
Format-Table IdentityReference, FileSystemRights,
AuditFlags, IsInherited, InheritanceFlags
Note that reading/modifying SACLs requires the appropriate security privileges. Audit Policy and SACL entries must both be configured for the required events to be generated.
Caution and Best Practices
When working with permissions, exercise caution to prevent unintentional security vulnerabilities. Here are some best practices:
- Test in a safe environment:ย Before making changes in a production environment, test your PowerShell scripts in a controlled and safe environment to ensure the desired results.
- Document changes:ย Maintain clear documentation of the changes you make to permissions, including who made the changes and the reasons behind them.
- Back up ACLs before modifying permissions: Before changing NTFS permissions, save the security descriptor so you can restore the previous owner and DACL if necessary. Our example below backs up a single file/folder. For directory trees, you should use icacls /save and /restore and test the recovery procedure before using it in production. Don’t forget to protect the backup file because it contains security config data. Our example preserves the owner and DACL; SACL auditing rules require a separate backup procedure with the appropriate privileges.
$path = 'C:\demo\dir1'
$backupPath = 'C:\ACLBackup\dir1.sddl'
$backupDir = Split-Path -Parent $backupPath
New-Item -Path $backupDir -ItemType Directory -Force |
Out-Null
$acl = Get-Acl -LiteralPath $path -ErrorAction Stop
$acl.GetSecurityDescriptorSddlForm(
[System.Security.AccessControl.AccessControlSections]::Owner -bor
[System.Security.AccessControl.AccessControlSections]::Access
) |
Set-Content -LiteralPath $backupPath -Encoding ASCII -ErrorAction StopRestoring the ACL from a Backup
Use the following commands in order to restore the owner and DACL from a previously saved security descriptor. Run this procedure only when you need to revert the changes and have checked that the backup belongs to the target object.
$path = 'C:\demo\dir1'
$backupPath = 'C:\ACLBackup\dir1.sddl'
if (-not (Test-Path -LiteralPath $backupPath -PathType Leaf)) {
throw "Backup file not found: $backupPath"
}
$sddl = (Get-Content -LiteralPath $backupPath -Raw -ErrorAction Stop).Trim()
if ([string]::IsNullOrWhiteSpace($sddl)) {
throw "Backup file is empty: $backupPath"
}
$acl = Get-Acl -LiteralPath $path -ErrorAction Stop
$sections = (
[System.Security.AccessControl.AccessControlSections]::Owner -bor
[System.Security.AccessControl.AccessControlSections]::Access
)
$acl.SetSecurityDescriptorSddlForm($sddl, $sections)
Set-Acl -LiteralPath $path -AclObject $acl -ErrorAction Stop
# Verify the restored owner and DACL.
$restoredAcl = Get-Acl -LiteralPath $path -ErrorAction Stop
$restoredAcl | Format-List Owner, AreAccessRulesProtected
$restoredAcl.Access |
Format-Table IdentityReference, FileSystemRights,
AccessControlType, IsInheritedImportant to mention that the above procedure restores the owner and DACL saved in the backup. It does not restore SACL auditing rules/provide a complete backup and recovery solution for directory trees. You should verify the resulting permissions before returning the object to production use.
- Least privilege: Follow the principle of least privilege, granting only the necessary permissions to users and groups to minimize security risks.
Alternative Tools for NTFS Permissions Management
The built-in Get-Acl and Set-Acl cmdlets are sufficient for many NTFS permission management tasks. The following tools can simplify specific admin workflows:
- NTFSSecurity PowerShell module โ a community PowerShell module that adds cmdlets for managing NTFS permissions, inheritance, auditing rules, and effective access. Its established stable release is old, although newer prerelease builds are available. Evaluate the specific version, maintenance status, dependencies, and compatibility with your environment before using it in new automation.
- icacls.exe โ a built-in command-line tool for common NTFS ACL operations, including bulk permission changes.
- SMB share permissions โ use Get-SmbShareAccess to inspect share-level permissions and Grant-SmbShareAccess to grant them. These cmdlets manage SMB share permissions, not NTFS ACLs.
PowerShell vs. icacls.exe
- You should use Get-Acl and Set-Acl when you need to inspect/modify security descriptors in PowerShell scripts, integrate ACL changes with other admin logic, or handle individual ACEs.
- Use icacls.exe for common command-line ACL operations, including bulk and recursive permission changes. It can also be useful in deployment and migration scripts.
Neither tool is inherently better for production file servers. Choose based on the required operation, validation and rollback requirements, and the team’s automation standards. Don’t forget to test permission changes before applying them to production data.
Conclusion
Get-Acl and Set-Acl provide native PowerShell access to NTFS security descriptors, but they do not calculate effective user permissions. Before applying changes to production systems, you should preserve the original ACL, modify only the required entries, and check the resulting permissions.
What does the Get-Acl cmdlet do in PowerShell?
The Get-Acl cmdlet retrieves the security descriptor of a file or folder, including the owner, group, access rules (ACEs), and auditing settings.
What does the Set-Acl cmdlet do?
The Set-Acl cmdlet applies a modified ACL to a file or folder, allowing you to change ownership, add or remove permissions, configure inheritance, or update auditing settings.
Are Get-Acl and Set-Acl supported in both Windows PowerShell and PowerShell 7?
Yes. Both cmdlets are available in Windows PowerShell 5.1 and PowerShell 7+, although PowerShell 7 may behave differently in remoting and serialization scenarios.
How do I change the owner of a file or folder with PowerShell?
Retrieve the ACL with Get-Acl, modify the owner using the SetOwner() method, and then apply the changes with Set-Acl.


