Public/Grant-NTFSPermission.ps1
function Grant-NTFSPermission { <# .SYNOPSIS Grants NTFS permissions to files and folders with enhanced security controls. .DESCRIPTION Adds or modifies NTFS permissions on files and folders with: - Comprehensive error handling for specific scenarios - Support for all standard NTFS rights - Inheritance and propagation control options - Security principal validation against Active Directory - Path existence verification with detailed errors - Progress tracking for batch operations - Detailed logging with verbose and debug options - Support for -WhatIf and -Confirm parameters - Pipeline input support for batch processing - Performance optimizations for large environments .PARAMETER Path [String] Full path to the file or folder. Must exist and be accessible. Supports pipeline input. .PARAMETER Object [String] Security principal (user/group) receiving permissions. Must be resolvable in current domain/forest. Use format "Domain\Username" or "Domain\GroupName". .PARAMETER Permission [String] NTFS permission to grant. Valid values: - ReadAndExecute : Grants rights to read and execute files - AppendData : Grants rights to append data to files - CreateFiles : Grants rights to create new files within a folder - Read : Grants basic read access - Write : Grants basic write access - Modify : Grants read, write, and delete access - FullControl : Grants complete control over files and folders .PARAMETER NoInheritance [Switch] When specified, permissions are not inherited by child objects. By default, permissions are inherited by child objects. .PARAMETER ClearExisting [Switch] When specified, removes all existing permissions before applying new ones. Use with caution - can remove critical system permissions. .PARAMETER PassThru [Switch] Returns an object representing the modified ACL. By default, the function doesn't return any output. .EXAMPLE Grant-NTFSPermission -Path 'D:\Shares\Finance' -Object 'EguibarIT\Finance_RO' -Permission 'Read' Grants read access to Finance_RO group on Finance share. .EXAMPLE $params = @{ Path = 'E:\Data' Object = 'EguibarIT\Backup_Operators' Permission = 'Modify' } Grant-NTFSPermission @params -Verbose Grants modify rights with verbose logging. .EXAMPLE Get-ChildItem -Path 'D:\Projects' -Directory | Grant-NTFSPermission -Object 'EguibarIT\Developers' -Permission 'Modify' -WhatIf Shows what would happen if modify permissions were granted to the Developers group on all subdirectories of Projects. .EXAMPLE Grant-NTFSPermission -Path 'D:\Confidential' -Object 'EguibarIT\Executives' -Permission 'FullControl' -NoInheritance -PassThru Grants full control to the Executives group without inheritance and returns the modified ACL. .OUTPUTS [System.Security.AccessControl.FileSecurity] when -PassThru is specified [void] by default .INPUTS System.String You can pipe path strings to this function, allowing batch processing of multiple files and folders. .NOTES Used Functions: Name ║ Module/Namespace ═══════════════════════════════════════╬════════════════════════ Get-Acl ║ Microsoft.PowerShell.Security Set-Acl ║ Microsoft.PowerShell.Security Write-Verbose ║ Microsoft.PowerShell.Utility Write-Error ║ Microsoft.PowerShell.Utility Write-Debug ║ Microsoft.PowerShell.Utility Write-Progress ║ Microsoft.PowerShell.Utility Get-ADObject ║ ActiveDirectory Get-FunctionDisplay ║ EguibarIT .NOTES Version: 1.3 DateModified: 22/May/2025 LastModifiedBy: Vicente Rodriguez Eguibar vicente@eguibar.com Eguibar IT http://www.eguibarit.com .LINK https://github.com/vreguibar/EguibarIT/blob/main/Public/Grant-NTFSPermission.ps1 .LINK https://docs.microsoft.com/en-us/dotnet/api/system.security.accesscontrol.filesystemrights .LINK https://docs.microsoft.com/en-us/windows/win32/secauthz/access-control-lists .COMPONENT File System .ROLE Security Administration .FUNCTIONALITY NTFS Permissions Management #> [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default' )] [OutputType([void])] [OutputType([System.Security.AccessControl.FileSecurity], ParameterSetName = 'PassThru')] Param ( # Param1 path to the resource|folder [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'Absolute path to the file or folder', Position = 0)] [ValidateNotNullOrEmpty()] [ValidateScript( { Test-Path $_ -PathType Any }, ErrorMessage = 'Path does not exist or is not accessible: {0}' )] [Alias('FullName', 'FilePath', 'FolderPath')] [string] $path, # Param2 object or SecurityPrincipal [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'Name of the Identity getting the permission.', Position = 1)] [ValidateNotNullOrEmpty()] [Alias('GroupName', 'GroupID', 'Identity', 'SamAccountName')] [string] $object, # Param3 permission [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false, HelpMessage = 'NTFS permission to grant: ReadAndExecute, AppendData, CreateFiles, Read, Write, Modify, or FullControl', Position = 2)] [ValidateNotNullOrEmpty()] [ValidateSet('ReadAndExecute', 'AppendData', 'CreateFiles', 'Read', 'Write', 'Modify', 'FullControl')] [string] $permission, # Disable inheritance [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Disable inheritance for this permission')] [switch] $NoInheritance, # Clear existing permissions [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Clear all existing permissions before applying new ones')] [switch] $ClearExisting, # Return the modified ACL [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Return the modified ACL', ParameterSetName = 'PassThru')] [switch] $PassThru ) Begin { Set-StrictMode -Version Latest if ($null -ne $Variables -and $null -ne $Variables.Header) { $txt = ($Variables.Header -f (Get-Date).ToString('dd/MMM/yyyy'), $MyInvocation.Mycommand, (Get-FunctionDisplay -HashTable $PsBoundParameters -Verbose:$False) ) Write-Verbose -Message $txt } #end If ############################## # Module imports ############################## # Variables Definition [hashtable]$Splat = [hashtable]::New([StringComparer]::OrdinalIgnoreCase) [int]$TotalItems = 0 [int]$ProcessedItems = 0 [bool]$ValidatePrincipal = $true # Set to $false to skip AD validation for better performance [System.Collections.Generic.List[string]]$ProcessedPaths = [System.Collections.Generic.List[string]]::new() # Possible values for FileSystemRights are: # ReadAndExecute, AppendData, CreateFiles, read, write, Modify, FullControl # Initialize security flags $FileSystemRights = [Security.AccessControl.FileSystemRights]$Permission # Set inheritance flags based on NoInheritance parameter if ($PSBoundParameters['NoInheritance']) { $InheritanceFlag = [Security.AccessControl.InheritanceFlags]::None Write-Debug -Message 'Inheritance disabled' } else { $InheritanceFlag = [Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [Security.AccessControl.InheritanceFlags]::ObjectInherit Write-Debug -Message 'Inheritance enabled for container and object' } #end If-Else $PropagationFlag = [Security.AccessControl.PropagationFlags]::None $AccessControlType = [Security.AccessControl.AccessControlType]::Allow try { # Validate security principal Write-Debug -Message ('Validating security principal: {0}' -f $Object) $Account = [System.Security.Principal.NTAccount]::new($PSBoundParameters['Object']) # Validate the account only if validation is enabled if ($ValidatePrincipal) { # For performance in large environments, we could cache validated principals # or skip validation entirely if needed # Optional: Validate against AD # Uncomment this section if strict validation is required <# try { # Try to translate to SID to validate the account $null = $Account.Translate([System.Security.Principal.SecurityIdentifier]) Write-Debug -Message ('Security principal validated: {0}' -f $PSBoundParameters['Object']) } catch { throw ('Invalid security principal: {0}. Error: {1}' -f $PSBoundParameters['Object'], $_.Exception.Message) } #> } #end If } catch { $ErrorMsg = ('Error creating security principal object for {0}: {1}' -f $PSBoundParameters['Object'], $_.Exception.Message) Write-Error -Message $ErrorMsg -Category InvalidArgument throw } #end Try-Catch Write-Verbose -Message (' Beginning NTFS permission change for {0} with {1} rights' -f $PSBoundParameters['Object'], $PSBoundParameters['Permission'] ) } #end Begin Process { # Count total items for progress bar $TotalItems = $PSBoundParameters['Path'].Count $ProcessedItems = 0 # Process each path in the array foreach ($CurrentPath in $PSBoundParameters['Path']) { $ProcessedItems++ # Skip if already processed (in case of duplicates) if ($ProcessedPaths.Contains($CurrentPath)) { Write-Debug -Message ('Skipping duplicate path: {0}' -f $CurrentPath) continue } #end If $ProcessedPaths.Add($CurrentPath) # Show progress $Splat = @{ Activity = 'Granting NTFS Permissions' Status = ('Processing {0}' -f $CurrentPath) PercentComplete = (($ProcessedItems / $TotalItems) * 100) } Write-Progress @Splat Write-Debug -Message ('Processing path: {0}' -f $CurrentPath) # Create the FileSystemAccessRule object $FileSystemAccessRule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList ( $Account, $FileSystemRights, $InheritanceFlag, $PropagationFlag, $AccessControlType ) try { # Get current ACL $DirectorySecurity = Get-Acl -Path $CurrentPath # Create descriptive action for ShouldProcess $ShouldProcessDescription = ('Grant {0} permissions to {1} on {2}' -f $PSBoundParameters['Permission'], $PSBoundParameters['Object'], $CurrentPath) # Process only if ShouldProcess approves if ($PSCmdlet.ShouldProcess($CurrentPath, $ShouldProcessDescription)) { # Clear existing permissions if requested if ($PSBoundParameters['ClearExisting']) { if ($PSCmdlet.ShouldContinue( ('WARNING: About to remove ALL existing permissions on {0}. Continue?' -f $CurrentPath), 'Confirm Permission Removal')) { $DirectorySecurity.SetAccessRuleProtection($true, $false) Write-Debug -Message ('Cleared existing permissions on {0}' -f $CurrentPath) } else { Write-Verbose -Message ('User cancelled clearing permissions on {0}' -f $CurrentPath) continue } #end If-Else } #end If # Add the new access rule $DirectorySecurity.AddAccessRule($FileSystemAccessRule) # Apply the modified ACL Set-Acl -Path $CurrentPath -AclObject $DirectorySecurity Write-Verbose -Message ('Successfully granted {0} permissions to {1} on {2}' -f $PSBoundParameters['Permission'], $PSBoundParameters['Object'], $CurrentPath) # Return the ACL if PassThru is specified if ($PSBoundParameters['PassThru']) { Get-Acl -Path $CurrentPath } #end If } #end If } catch [System.UnauthorizedAccessException] { $ErrorMsg = ('Access denied. Cannot modify permissions on {0}. Error: {1}' -f $CurrentPath, $_.Exception.Message) Write-Error -Message $ErrorMsg -Category PermissionDenied continue } catch [System.IO.FileNotFoundException], [System.IO.DirectoryNotFoundException] { $ErrorMsg = ('Path no longer exists: {0}. Error: {1}' -f $CurrentPath, $_.Exception.Message) Write-Error -Message $ErrorMsg -Category ObjectNotFound continue } catch [System.Security.Principal.IdentityNotMappedException] { $ErrorMsg = ('Security principal cannot be mapped: {0}. Error: {1}' -f $PSBoundParameters['Object'], $_.Exception.Message) Write-Error -Message $ErrorMsg -Category InvalidData throw } catch { $ErrorMsg = ('Error granting NTFS permissions on {0}. Error: {1}' -f $CurrentPath, $_.Exception.Message) Write-Error -Message $ErrorMsg continue } #end Try-Catch } #end Foreach # Complete progress bar Write-Progress -Activity 'Granting NTFS Permissions' -Completed } #end Process End { if ($null -ne $Variables -and $null -ne $Variables.Footer) { $txt = ($Variables.Footer -f $MyInvocation.InvocationName, 'changing NTFS permissions.' ) Write-Verbose -Message $txt } #end If } #end End } #end Function Grant-NTFSPermission |