Private/Security/Invoke-RemovalVerification.ps1

#Requires -Version 5.1

<#
.SYNOPSIS
    Post-operation verification module for SID removal operations
 
.DESCRIPTION
    Provides focused verification functionality to confirm that SID removal
    operations were successful. This module implements the "tool" pattern
    for reliable post-operation validation with comprehensive error handling.
 
    This module focuses solely on verification logic, enabling independent
    testing and reuse across different removal and validation 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 verification failures: .\Troubleshooting\Common\Verification-Issues.md
    - For AD replication delays: .\Troubleshooting\Common\AD-Replication-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 Invoke-RemovalVerification {
    <#
    .SYNOPSIS
        Verifies successful completion of SID removal operations
 
    .DESCRIPTION
        Performs comprehensive post-removal verification by checking that specified
        SIDs are no longer present in the Active Directory object's ACL. This function
        implements the "tool" pattern for focused verification with enterprise reliability.
 
        The verification process includes:
        - AD replication delay accommodation
        - Retry logic for transient failures
        - Comprehensive result reporting
        - Detailed logging with correlation tracking
 
    .PARAMETER ObjectDN
        Distinguished name of the Active Directory object to verify.
        Object must exist and be accessible with current credentials.
 
    .PARAMETER AllowedSIDs
        Array of Security Identifiers that should have been removed.
        Verification checks that none of these SIDs remain in the ACL.
 
    .PARAMETER CorrelationId
        Unique identifier for tracking this verification operation
        across logs and audit trails. Generated automatically if not provided.
 
    .EXAMPLE
        PS> $result = Invoke-RemovalVerification -ObjectDN "CN=TestUser,CN=Users,DC=contoso,DC=com" -AllowedSIDs @("S-1-5-21-1234567890-1001")
 
        DESCRIPTION: Verifies that a single SID was successfully removed
        OUTPUT: PSCustomObject with Success status and verification details
        USE CASE: Basic verification after targeted SID removal
 
    .EXAMPLE
        PS> $verification = Invoke-RemovalVerification -ObjectDN $dn -AllowedSIDs $removedSIDs -CorrelationId $id
        if ($verification.Success) {
            Write-Output "All SIDs successfully removed"
        } else {
            Write-Warning "Verification failed: $($verification.ErrorMessage)"
        }
 
        DESCRIPTION: Comprehensive verification with result handling
        OUTPUT: Structured verification results for automation
        USE CASE: Enterprise automation with conditional processing
 
    .OUTPUTS
        [PSCustomObject] Object containing:
        - Success: Boolean indicating overall verification success
        - ErrorMessage: Detailed error information if verification fails
        - RemainingOrphanedSIDs: Array of SIDs that remain in the ACL
 
    .NOTES
        RELIABILITY FEATURES:
        - AD replication delay accommodation (500ms pause)
        - Retry logic for transient AD failures
        - Comprehensive error handling and logging
        - Detailed result reporting for troubleshooting
 
        PERFORMANCE CHARACTERISTICS:
        - Verification Time: 500ms+ per object (includes replication delay)
        - Retry Logic: Up to 3 attempts with backoff
        - Memory Usage: Minimal - focused ACL analysis
 
        VERIFICATION PROCESS:
        - Brief pause for AD replication consistency
        - Retrieve current ACL state from AD
        - Compare against expected removal list
        - Report any remaining orphaned SIDs
 
        TROUBLESHOOTING:
        - For remaining SIDs: Check AD replication and timing
        - For ACL access errors: Verify appropriate permissions
        - For verification timeouts: Consider network and AD performance
    #>


    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ObjectDN,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string[]]$AllowedSIDs,

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

    try {
        Write-StructuredLog "Starting removal verification for $ObjectDN (Verifying $($AllowedSIDs.Count) SIDs)" -Level Debug -CorrelationId $CorrelationId

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

        # Brief pause for AD replication consistency
        Start-Sleep -Milliseconds 500

        # Get current ACL for verification with retry logic
        $verificationACL = Invoke-ADOperationWithRetry -ScriptBlock {
            Get-Acl -Path "AD:\$($ObjectDN.Trim())" -ErrorAction Stop
        } -MaxRetries 3 -OperationName 'Get-ACL-Verification' -ObjectContext $ObjectDN

        # Check for remaining orphaned SIDs
        $remainingOrphanedSIDs = @()
        foreach ($ace in $verificationACL.Access) {
            if ($AllowedSIDs -contains $ace.IdentityReference.Value) {
                $remainingOrphanedSIDs += $ace.IdentityReference.Value
            }
        }

        # Prepare verification results
        if ($remainingOrphanedSIDs.Count -eq 0) {
            Write-StructuredLog "Verification successful: All $($AllowedSIDs.Count) SIDs removed from $ObjectDN" -Level Verbose -CorrelationId $CorrelationId
            return [PSCustomObject]@{
                Success = $true
                ErrorMessage = $null
                RemainingOrphanedSIDs = @()
            }
        } else {
            $errorMessage = "Verification failed: $($remainingOrphanedSIDs.Count) of $($AllowedSIDs.Count) SIDs still present"
            Write-StructuredLog "Verification failed for $ObjectDN : $errorMessage" -Level Warning -CorrelationId $CorrelationId
            return [PSCustomObject]@{
                Success = $false
                ErrorMessage = $errorMessage
                RemainingOrphanedSIDs = $remainingOrphanedSIDs
            }
        }
    }
    catch {
        $errorMessage = "Verification error: $($_.Exception.Message)"
        Write-StructuredLog "Verification error for $ObjectDN : $errorMessage" -Level Error -CorrelationId $CorrelationId
        return [PSCustomObject]@{
            Success = $false
            ErrorMessage = $errorMessage
            RemainingOrphanedSIDs = @()
        }
    }
}