Private/ACL/Get-ACLForRemoval.ps1

#Requires -Version 5.1

<#
.SYNOPSIS
    ACL retrieval module for Active Directory object security descriptors
 
.DESCRIPTION
    Provides focused ACL retrieval functionality with enterprise-grade retry logic
    and error handling. This module implements the "tool" pattern for reliable
    ACL retrieval operations from Active Directory objects.
 
    This module focuses solely on ACL retrieval, enabling independent testing
    and reuse across different removal and modification scenarios.
 
.NOTES
    Author: Jeffrey Stuhr
    Blog: https://www.techbyjeff.net
    LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/
    Last Updated: 2025-01-27
    Version: 1.0.0
    PowerShell Version: 5.1+ (Windows), 7.x+ (Cross-platform)
 
    TROUBLESHOOTING:
    - For ACL retrieval failures: .\Troubleshooting\Common\ACL-Retrieval-Issues.md
    - For AD connectivity issues: .\Troubleshooting\Common\AD-Connection-Issues.md
 
    DEPENDENCIES:
    - Requires Logging.ps1 for Write-StructuredLog function
    - Requires Operations\Invoke-ADOperationWithRetry.ps1 for retry logic
    - Active Directory PowerShell module for Get-Acl cmdlet
#>


function Get-ACLForRemoval {
    <#
    .SYNOPSIS
        Retrieves ACL from Active Directory objects with retry logic
 
    .DESCRIPTION
        Safely retrieves Active Directory ACL objects with comprehensive error
        handling and retry logic for enterprise reliability. This function
        implements the "tool" pattern for focused ACL retrieval operations.
 
        The function includes:
        - Retry logic for transient AD failures
        - Parameter validation and error handling
        - Comprehensive logging with correlation tracking
        - Optimized for removal operation workflows
 
    .PARAMETER ObjectDistinguishedName
        Distinguished name of the Active Directory object to retrieve ACL from.
        Object must exist and be accessible with current credentials.
 
    .PARAMETER CorrelationId
        Unique identifier for tracking this ACL retrieval operation
        across logs and audit trails. Generated automatically if not provided.
 
    .EXAMPLE
        PS> $acl = Get-ACLForRemoval -ObjectDistinguishedName "CN=TestUser,CN=Users,DC=contoso,DC=com"
 
        DESCRIPTION: Retrieves ACL from a user object
        OUTPUT: System.DirectoryServices.ActiveDirectorySecurity object
        USE CASE: Basic ACL retrieval for removal operations
 
    .EXAMPLE
        PS> $acl = Get-ACLForRemoval -ObjectDistinguishedName $dn -CorrelationId $correlationId
        if ($acl) {
            Write-Output "Retrieved ACL with $($acl.Access.Count) access rules"
        }
 
        DESCRIPTION: ACL retrieval with correlation tracking and validation
        OUTPUT: ACL object ready for modification operations
        USE CASE: Enterprise automation with audit trail requirements
 
    .OUTPUTS
        [System.DirectoryServices.ActiveDirectorySecurity] Active Directory ACL object
        ready for modification operations, or $null if retrieval fails.
 
    .NOTES
        RELIABILITY FEATURES:
        - Retry logic with exponential backoff for transient failures
        - Comprehensive parameter validation
        - Detailed error handling and logging
        - Optimized for removal operation workflows
 
        PERFORMANCE CHARACTERISTICS:
        - Retrieval Time: 50-200ms per object typically
        - Retry Logic: Up to 3 attempts with backoff
        - Memory Usage: Minimal - direct ACL retrieval
 
        SECURITY CONSIDERATIONS:
        - Requires appropriate AD permissions for ACL reading
        - All operations logged with correlation IDs for audit trails
        - Validates object existence before ACL retrieval
 
        TROUBLESHOOTING:
        - For permission errors: Verify AD read rights
        - For connectivity issues: Check AD server availability
        - For object not found: Verify DN format and object existence
    #>


    [CmdletBinding()]
    [OutputType([System.DirectoryServices.ActiveDirectorySecurity])]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ObjectDistinguishedName,

        [Parameter()]
        [string]$CorrelationId = [System.Guid]::NewGuid().ToString()
    )

    try {
        Write-StructuredLog "Starting ACL retrieval for $ObjectDistinguishedName" -Level Debug -CorrelationId $CorrelationId

        # Validate ObjectDistinguishedName parameter
        if ([string]::IsNullOrWhiteSpace($ObjectDistinguishedName.Trim())) {
            throw "ObjectDistinguishedName parameter cannot be empty or whitespace"
        }

        # Security validation - detect path traversal attempts
        if ($ObjectDistinguishedName -match '\.\.') {
            throw "Path traversal detected in ObjectDistinguishedName: $ObjectDistinguishedName"
        }

        # Validate ObjectDistinguishedName format (basic validation for AD DN format)
        if ($ObjectDistinguishedName -match '^[A-Za-z]:\\' -and $ObjectDistinguishedName -notmatch '^CN=|^OU=|^DC=') {
            throw "Invalid ObjectDistinguishedName format. Expected AD Distinguished Name, got filesystem path: $ObjectDistinguishedName"
        }

        # Check if target object exists
        if (-not (Test-ValidDistinguishedName -DistinguishedName $ObjectDistinguishedName -CorrelationId $CorrelationId)) {
            throw "Target path not found: $ObjectDistinguishedName"
        }

        # Retrieve ACL with retry logic for reliability
        $acl = Invoke-ADOperationWithRetry -ScriptBlock {
            Get-Acl -Path "AD:\$($ObjectDistinguishedName.Trim())" -ErrorAction Stop
        } -MaxRetries 3 -OperationName 'Get-ACL' -ObjectContext $ObjectDistinguishedName

        if (-not $acl) {
            throw "Failed to retrieve ACL for $ObjectDistinguishedName"
        }

        Write-StructuredLog "Successfully retrieved ACL for $ObjectDistinguishedName (Access rules: $($acl.Access.Count))" -Level Verbose -CorrelationId $CorrelationId
        return $acl
    }
    catch {
        Write-StructuredLog "Failed to retrieve ACL for $ObjectDistinguishedName : $($_.Exception.Message)" -Level Error -CorrelationId $CorrelationId
        throw
    }
}