Powershell/Private/Permissions/Set-RegPermission.ps1
|
function Set-RegPermission { param ( [Parameter(Mandatory)] [string]$SourceSID, [Parameter(Mandatory)] [string]$TargetSID, [Parameter(Mandatory)] [string]$FilePath, [switch]$Recursive, [int]$ProgressHeartbeatIntervalSeconds = 0, [scriptblock]$OnProgressHeartbeat ) function local:Get-IcaclsProcessExitCode { param( [Parameter(Mandatory = $true)] [System.Diagnostics.Process]$Process ) if (-not $Process.HasExited) { $Process.WaitForExit() | Out-Null } $Process.Refresh() $exitCode = $Process.ExitCode if ($null -eq $exitCode) { return 0 } return [int]$exitCode } function local:Invoke-IcaclsSafe { param( [Parameter(Mandatory = $true)] [string]$Path, [Parameter(Mandatory = $true)] [string[]]$Arguments ) $local:ErrorActionPreference = 'Continue' $output = & icacls.exe $Path $Arguments 2>&1 | ForEach-Object { "$_" } $script:IcaclsExitCode = if ($null -ne $LASTEXITCODE) { [int]$LASTEXITCODE } else { 0 } return $output } function local:Invoke-IcaclsWithHeartbeat { param( [Parameter(Mandatory = $true)] [string]$Path, [Parameter(Mandatory = $true)] [string[]]$Arguments, [int]$HeartbeatIntervalSeconds, [scriptblock]$OnHeartbeat ) if ([string]::IsNullOrWhiteSpace($Path)) { throw "Invoke-IcaclsWithHeartbeat requires a non-empty Path. Received: '$Path'" } $local:ErrorActionPreference = 'Continue' $argumentList = @($Path) + $Arguments $process = Start-Process -FilePath 'icacls.exe' -ArgumentList $argumentList -PassThru -NoNewWindow -Wait:$false if ($HeartbeatIntervalSeconds -gt 0 -and $OnHeartbeat) { $intervalMs = [math]::Max(1, $HeartbeatIntervalSeconds) * 1000 while (-not $process.HasExited) { if ($process.WaitForExit($intervalMs)) { break } & $OnHeartbeat } } $script:IcaclsExitCode = Get-IcaclsProcessExitCode -Process $process $process.Dispose() return @() } if ([string]::IsNullOrWhiteSpace($FilePath)) { throw 'Set-RegPermission requires a non-empty FilePath.' } if (-not (Test-Path -LiteralPath $FilePath)) { throw "Set-RegPermission path does not exist: $FilePath" } $script:IcaclsExitCode = 0 $useProgressHeartbeat = $Recursive -and $ProgressHeartbeatIntervalSeconds -gt 0 -and $null -ne $OnProgressHeartbeat $ntfsPermissionLogPath = Join-Path $(if (-not [string]::IsNullOrWhiteSpace($env:SystemDrive)) { $env:SystemDrive } else { 'C:' }) 'Windows\Temp\jcAdmu.log' # Create SecurityIdentifier objects $SourceSIDObj = New-Object System.Security.Principal.SecurityIdentifier($SourceSID) $TargetSIDObj = New-Object System.Security.Principal.SecurityIdentifier($TargetSID) # Get NTAccount names for logging and ACLs $SourceAccountTranslated = $false $TargetAccountTranslated = $false try { $SourceAccount = $SourceSIDObj.Translate([System.Security.Principal.NTAccount]).Value $SourceAccountTranslated = $true } catch { Write-ToLog "Warning: Could not translate SourceSID $SourceSID to NTAccount. Using SID string instead." -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath $SourceAccount = $SourceSID } try { $TargetAccount = $TargetSIDObj.Translate([System.Security.Principal.NTAccount]).Value $TargetAccountTranslated = $true } catch { Write-ToLog "Warning: Could not translate TargetSID $TargetSID to NTAccount. Using SID string instead." -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath $TargetAccount = $TargetSID } $scopeLabel = if ($Recursive) { 'recursive' } else { 'immediate level only' } try { Write-ToLog -Message "Starting permission migration from $SourceAccount to $TargetAccount on path: $FilePath ($scopeLabel)" -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath Write-ToLog -Message "Log messages below are streamed from standard output of the icacls command, output may be ignored if it contains errors about pointers *" -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath } catch { Write-ToLog -Message "Failed to initialize NTFS permission log at $ntfsPermissionLogPath $($_.Exception.Message)" -Level Warning -Step "Set-RegPermission" } # Prepare icacls-compatible account identifiers (SIDs need * prefix) $SourceAccountIcacls = if ($SourceAccountTranslated) { $SourceAccount } else { "*$SourceAccount" } $TargetAccountIcacls = if ($TargetAccountTranslated) { $TargetAccount } else { "*$TargetAccount" } # Add the targetAccount to the ACL if it doesn't already exist $acl = Get-Acl -LiteralPath $FilePath $targetMember = $acl.Access | Where-Object { $_.IdentityReference -eq $TargetAccount } if (-not $targetMember) { $newRule = New-Object System.Security.AccessControl.FileSystemAccessRule( $TargetAccount, "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow" ) $acl.AddAccessRule($newRule) Set-Acl -LiteralPath $FilePath -AclObject $acl } # Use icacls for bulk operations - much faster than PowerShell ACL cmdlets Write-ToLog "Starting permission migration using icacls for path: $FilePath" -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath $grantArguments = if ($Recursive) { @('/grant', "${TargetAccountIcacls}:(OI)(CI)F", '/T', '/C', '/Q') } else { @('/grant', "${TargetAccountIcacls}:(OI)(CI)F", '/C', '/Q') } $ownerArguments = if ($Recursive) { @('/setowner', "$TargetAccountIcacls", '/T', '/C', '/Q') } else { @('/setowner', "$TargetAccountIcacls", '/C', '/Q') } # Step 1: Grant target user full control inheritance on folder Write-ToLog "Granting permissions to: $TargetAccountIcacls ($scopeLabel)" -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath $icaclsGrantResult = if ($useProgressHeartbeat) { Invoke-IcaclsWithHeartbeat -Path $FilePath -Arguments $grantArguments -HeartbeatIntervalSeconds $ProgressHeartbeatIntervalSeconds -OnHeartbeat $OnProgressHeartbeat } elseif ($Recursive) { Invoke-IcaclsSafe -Path $FilePath -Arguments $grantArguments } else { & icacls.exe $FilePath $grantArguments 2>&1 | ForEach-Object { "$_" } $script:IcaclsExitCode = if ($null -ne $LASTEXITCODE) { [int]$LASTEXITCODE } else { 0 } } if ($icaclsGrantResult) { foreach ($line in $icaclsGrantResult) { if ($line -and $line.ToString().Trim()) { Write-ToLog " icacls output: $line" -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath } } } if ($script:IcaclsExitCode -ne 0) { Write-ToLog "Warning: icacls grant operation had issues. Exit code: $($script:IcaclsExitCode)" -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath } else { Write-ToLog "Successfully granted permissions to $TargetAccountIcacls ($scopeLabel)" -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath } # Step 2: Change ownership from source to target user Write-ToLog "Setting owner to $TargetAccountIcacls ($scopeLabel)" -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath $icaclsOwnerResult = if ($useProgressHeartbeat) { Invoke-IcaclsWithHeartbeat -Path $FilePath -Arguments $ownerArguments -HeartbeatIntervalSeconds $ProgressHeartbeatIntervalSeconds -OnHeartbeat $OnProgressHeartbeat } elseif ($Recursive) { Invoke-IcaclsSafe -Path $FilePath -Arguments $ownerArguments } else { & icacls.exe $FilePath $ownerArguments 2>&1 | ForEach-Object { "$_" } $script:IcaclsExitCode = if ($null -ne $LASTEXITCODE) { [int]$LASTEXITCODE } else { 0 } } if ($icaclsOwnerResult) { foreach ($line in $icaclsOwnerResult) { if ($line -and $line.ToString().Trim()) { Write-ToLog " icacls output: $line" -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath } } } if ($script:IcaclsExitCode -ne 0) { Write-ToLog "Warning: icacls setowner operation had issues. Exit code: $($script:IcaclsExitCode)" -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath } else { Write-ToLog "Successfully set owner to $TargetAccountIcacls ($scopeLabel)" -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath } Write-ToLog "Permission migration completed for path: $FilePath" -Level Verbose -Step "Set-RegPermission" -Path $ntfsPermissionLogPath } |