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. .INPUTS System.String You can pipe path strings to this function, allowing batch processing of multiple files and folders. .OUTPUTS [System.Security.AccessControl.FileSecurity] when -PassThru is specified [void] by default .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 |