Private/SID/Resolve-SIDIdentity.ps1

#Requires -Version 5.1

<#
.SYNOPSIS
    SID Identity Resolution Module for Find-UnknownSID Solution
 
.DESCRIPTION
    This focused module handles all SID identity resolution, validation, and translation
    for the Find-UnknownSID enterprise solution. It processes various identity reference
    types and performs comprehensive SID validation to detect orphaned accounts.
 
    SINGLE RESPONSIBILITY:
    This module does ONE thing well: Resolve and validate SID identities from various
    identity reference formats, including complex scenarios like orphaned account detection.
 
    BUSINESS VALUE:
    - Accurate identity resolution across different reference formats
    - Robust orphaned account detection with detailed analysis
    - Comprehensive error handling for translation failures
    - Support for complex Active Directory environments
 
    TECHNICAL FEATURES:
    - Multiple identity reference type handling (SecurityIdentifier, NTAccount, etc.)
    - Enhanced NTAccount translation with orphaned account detection
    - Comprehensive SID validation and classification
    - Intelligent string extraction from various identity formats
    - Integration with SID analysis and validation functions
 
.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 identity resolution issues: .\Troubleshooting\Security\Identity-Resolution-Issues.md
    - For NTAccount translation errors: .\Troubleshooting\Security\NTAccount-Translation-Issues.md
    - For SID validation problems: .\Troubleshooting\Security\SID-Validation-Issues.md
 
    DEPENDENCIES:
    - Requires Logging.ps1 for Write-StructuredLog function
    - Requires Test-SIDFormat.ps1 for SID format validation
    - Requires Test-OrphanedSID.ps1 for orphaned SID detection
    - Requires Get-SIDAnalysis.ps1 for SID analysis
    - Requires New-SIDResult.ps1 for result object creation
    - Uses script-scoped variables (CorrelationId for tracking)
#>


#region SID Identity Resolution Functions

function Test-AccessRuleForOrphanedSID {
    <#
    .SYNOPSIS
        Tests an individual access rule for orphaned SIDs with comprehensive validation
 
    .DESCRIPTION
        Orchestrates the testing of a single ACL access rule to determine if it contains
        orphaned SIDs. This function coordinates identity resolution, SID validation,
        and orphaned account detection across various identity reference formats.
 
        The function provides a streamlined workflow that:
        - Resolves identity references to SID strings
        - Validates SID formats and excludes well-known SIDs
        - Tests for orphaned status with detailed analysis
        - Creates comprehensive result objects for found orphaned SIDs
 
    .PARAMETER AccessRule
        The access rule to test for orphaned SIDs. Must contain an IdentityReference property.
 
    .PARAMETER ObjectDN
        Distinguished name of the Active Directory object containing the access rule.
        Used for context and logging purposes.
 
    .PARAMETER ObjectClass
        Class of the AD object (user, group, computer, etc.) for categorization
        and analysis context.
 
    .PARAMETER CurrentDomainSID
        The SID of the current domain for enhanced context analysis and SID categorization.
        Used to distinguish between local, foreign, and unknown domain SIDs.
 
    .PARAMETER CorrelationId
        Unique identifier for tracking this operation across logs and audit trails.
        Generated automatically if not provided.
 
    .EXAMPLE
        PS> $result = Test-AccessRuleForOrphanedSID -AccessRule $rule -ObjectDN $dn -ObjectClass "User"
 
        DESCRIPTION: Basic orphaned SID testing for a single access rule
        OUTPUT: OrphanedSIDResult object if orphaned SID found, null otherwise
        USE CASE: Standard security audit of individual access rules
 
    .EXAMPLE
        PS> $rules | ForEach-Object { Test-AccessRuleForOrphanedSID -AccessRule $_ -ObjectDN $dn -CurrentDomainSID $domainSID }
 
        DESCRIPTION: Batch processing of access rules with domain context
        OUTPUT: Array of OrphanedSIDResult objects for all orphaned SIDs found
        BUSINESS CASE: Comprehensive security analysis with domain categorization
 
    .EXAMPLE
        PS> Test-AccessRuleForOrphanedSID -AccessRule $rule -ObjectDN $dn -ObjectClass "Group" -CorrelationId $correlationId
 
        DESCRIPTION: Tracked processing with correlation ID for audit trails
        OUTPUT: OrphanedSIDResult with correlation tracking for compliance
        INTEGRATION: Enterprise environments requiring audit trail correlation
 
    .OUTPUTS
        [OrphanedSIDResult] If an orphaned SID is detected, returns a comprehensive
        result object containing detailed analysis and metadata. Returns null if
        no orphaned SID is found or if validation fails.
 
    .NOTES
        PROCESSING WORKFLOW:
        1. Validate access rule and extract identity reference
        2. Resolve identity reference to SID string using appropriate method
        3. Validate SID format and check for well-known SIDs
        4. Test for orphaned status using enhanced detection
        5. Create comprehensive result object with analysis
 
        IDENTITY REFERENCE SUPPORT:
        - SecurityIdentifier objects (direct SID access)
        - NTAccount objects (translation with orphaned account detection)
        - Deserialized identity references (background job support)
        - String-based identity references (flexible format handling)
 
        ERROR HANDLING:
        - Comprehensive validation of input parameters
        - Graceful handling of translation failures (indicates orphaned accounts)
        - Detailed logging for troubleshooting and audit purposes
        - Safe property access with null checking
 
        PERFORMANCE CHARACTERISTICS:
        - Optimized for single access rule processing
        - Efficient identity reference type detection
        - Minimal memory allocation for string operations
        - Fast validation using cached well-known SID lists
    #>


    [CmdletBinding()]
    [OutputType([OrphanedSIDResult])]
    param(
        [Parameter(Mandatory)]
        [PSObject]$AccessRule,

        [Parameter(Mandatory)]
        [string]$ObjectDN,

        [Parameter()]
        [string]$ObjectClass = "Unknown",

        [Parameter()]
        [string]$CurrentDomainSID,

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

    try {
        Write-StructuredLog "Starting access rule analysis for orphaned SID (Object: $ObjectDN)" -Level Debug -CorrelationId $CorrelationId

        # Validate access rule and identity reference
        if (-not $AccessRule -or -not $AccessRule.PSObject.Properties['IdentityReference'] -or -not $AccessRule.IdentityReference) {
            Write-StructuredLog "Skipping access rule with missing or null IdentityReference for $ObjectDN" -Level Debug -CorrelationId $CorrelationId
            return $null
        }

        # Resolve identity reference to SID string
        $sidString = Resolve-IdentityReference -IdentityReference $AccessRule.IdentityReference -ObjectDN $ObjectDN -CorrelationId $CorrelationId
        if (-not $sidString) {
            Write-StructuredLog "Failed to resolve identity reference to SID for $ObjectDN" -Level Debug -CorrelationId $CorrelationId
            return $null
        }

        # Validate SID format and check for well-known SIDs
        if (-not (Test-SIDValidityAndType -SIDString $sidString -CorrelationId $CorrelationId)) {
            Write-StructuredLog "SID validation failed or well-known SID detected: $sidString" -Level Debug -CorrelationId $CorrelationId
            return $null
        }

        Write-StructuredLog "SID $sidString passed validation, testing for orphaned status..." -Level Debug -CorrelationId $CorrelationId

        # Test for orphaned status and create result
        $isOrphaned = Test-OrphanedSID -SID $sidString
        Write-StructuredLog "Orphaned status for SID $sidString : $isOrphaned" -Level Debug -CorrelationId $CorrelationId

        if ($isOrphaned) {
            Write-StructuredLog "ORPHANED SID DETECTED: $sidString on $ObjectDN" -Level Verbose -CorrelationId $CorrelationId

            # Get detailed SID analysis
            $sidAnalysis = Get-SIDAnalysis -SIDString $sidString -CurrentDomainSID $CurrentDomainSID

            # Create comprehensive orphaned SID result
            return New-OrphanedSIDResult -ObjectDN $ObjectDN -ObjectClass $ObjectClass -OrphanedSID $sidString -AccessRule $AccessRule -LikelySource $sidAnalysis.LikelySource -Confidence $sidAnalysis.Confidence -Notes $sidAnalysis.Notes -ProcessingMethod "Identity-Resolution" -CorrelationId $CorrelationId
        } else {
            Write-StructuredLog "SID $sidString is valid (found in AD)" -Level Debug -CorrelationId $CorrelationId
            return $null
        }
    }
    catch {
        Write-StructuredLog "Error testing access rule for orphaned SID on $ObjectDN : $($_.Exception.Message)" -Level Warning -CorrelationId $CorrelationId
        return $null
    }
}

function Resolve-IdentityReference {
    <#
    .SYNOPSIS
        Resolves various identity reference types to SID strings
 
    .DESCRIPTION
        Handles the resolution of different identity reference formats to SID strings,
        including SecurityIdentifier objects, NTAccount objects, and various string
        formats. This function provides intelligent type detection and routing to
        appropriate resolution methods.
 
    .PARAMETER IdentityReference
        The identity reference object to resolve. Can be SecurityIdentifier, NTAccount,
        or other identity reference types including deserialized objects.
 
    .PARAMETER ObjectDN
        Distinguished name for context in error logging and troubleshooting.
 
    .PARAMETER CorrelationId
        Unique identifier for tracking this operation across logs.
 
    .OUTPUTS
        [string] The resolved SID string, or null if resolution fails
 
    .NOTES
        SUPPORTED IDENTITY TYPES:
        - SecurityIdentifier: Direct SID value extraction
        - NTAccount: Translation with orphaned account detection
        - Deserialized objects: Background job object handling
        - String references: Various string format processing
 
        PROCESSING STRATEGY:
        1. Detect identity reference type
        2. Route to appropriate resolution method
        3. Handle errors gracefully (orphaned accounts cause translation failures)
        4. Return validated SID string or null
    #>


    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [PSObject]$IdentityReference,

        [Parameter()]
        [string]$ObjectDN,

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

    try {
        Write-StructuredLog "Resolving identity reference type for $ObjectDN" -Level Debug -CorrelationId $CorrelationId

        # Handle SecurityIdentifier objects (direct SID access)
        if ($IdentityReference -is [System.Security.Principal.SecurityIdentifier]) {
            if ($IdentityReference.Value) {
                $sidString = $IdentityReference.Value
                Write-StructuredLog "Extracted SID from SecurityIdentifier: $sidString" -Level Debug -CorrelationId $CorrelationId
                return $sidString
            } else {
                Write-StructuredLog "SecurityIdentifier object has null Value property for $ObjectDN" -Level Debug -CorrelationId $CorrelationId
                return $null
            }
        }

        # Handle NTAccount objects (translation required)
        if ($IdentityReference -is [System.Security.Principal.NTAccount]) {
            return Convert-NTAccountToSID -NTAccount $IdentityReference -ObjectDN $ObjectDN -CorrelationId $CorrelationId
        }

        # Handle other identity reference types and deserialized objects
        $stringValue = Get-StringFromIdentityReference -IdentityReference $IdentityReference -ObjectDN $ObjectDN -CorrelationId $CorrelationId
        if ([string]::IsNullOrWhiteSpace($stringValue)) {
            return $null
        }

        # Validate extracted string as SID format
        if (Test-SIDFormat -SID $stringValue) {
            Write-StructuredLog "Extracted valid SID from identity reference: $stringValue" -Level Debug -CorrelationId $CorrelationId
            return $stringValue
        } else {
            Write-StructuredLog "Extracted string is not a valid SID format: $stringValue (Type: $($IdentityReference.GetType().Name)) for $ObjectDN" -Level Debug -CorrelationId $CorrelationId
            return $null
        }
    }
    catch {
        Write-StructuredLog "Error resolving identity reference for $ObjectDN : $($_.Exception.Message)" -Level Warning -CorrelationId $CorrelationId
        return $null
    }
}

function Convert-NTAccountToSID {
    <#
    .SYNOPSIS
        Converts NTAccount objects to SID strings with orphaned account detection
 
    .DESCRIPTION
        Handles the translation of NTAccount objects to SID strings, with special
        handling for orphaned accounts. When translation fails, this indicates an
        orphaned account and the function creates an appropriate result object.
 
    .PARAMETER NTAccount
        The NTAccount object to translate to a SID
 
    .PARAMETER ObjectDN
        Distinguished name for context in logging
 
    .PARAMETER CorrelationId
        Unique identifier for tracking this operation
 
    .OUTPUTS
        [string] The translated SID string, or a special orphaned account marker
        if translation fails due to orphaned account
 
    .NOTES
        TRANSLATION BEHAVIOR:
        - Successful translation returns SID string
        - Failed translation indicates orphaned account
        - Creates orphaned result object for untranslatable accounts
        - Comprehensive error logging for troubleshooting
 
        ORPHANED ACCOUNT DETECTION:
        Translation failures typically indicate:
        - Deleted user or group accounts
        - Moved accounts from other domains
        - Corrupted or invalid account references
    #>


    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [System.Security.Principal.NTAccount]$NTAccount,

        [Parameter()]
        [string]$ObjectDN,

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

    try {
        if (-not $NTAccount.Value) {
            Write-StructuredLog "NTAccount object has null Value property for $ObjectDN" -Level Debug -CorrelationId $CorrelationId
            return $null
        }

        Write-StructuredLog "Attempting to translate NTAccount: '$($NTAccount.Value)'" -Level Debug -CorrelationId $CorrelationId

        $translatedSID = $NTAccount.Translate([System.Security.Principal.SecurityIdentifier])

        if ($translatedSID -and $translatedSID.Value) {
            $sidString = $translatedSID.Value
            Write-StructuredLog "Successfully translated NTAccount '$($NTAccount.Value)' to SID: $sidString" -Level Debug -CorrelationId $CorrelationId
            return $sidString
        } else {
            Write-StructuredLog "Translation of NTAccount '$($NTAccount.Value)' returned null SID" -Level Debug -CorrelationId $CorrelationId
            return $null
        }
    }
    catch {
        # Translation failed - this indicates an orphaned account
        Write-StructuredLog "ORPHANED ACCOUNT DETECTED: Failed to translate NTAccount '$($NTAccount.Value)' to SID - likely orphaned: $($_.Exception.Message)" -Level Information -CorrelationId $CorrelationId

        # Return special marker for orphaned account names
        # This will be handled by the calling function to create proper result object
        return "ORPHANED_ACCOUNT:$($NTAccount.Value)"
    }
}

function Test-SIDValidityAndType {
    <#
    .SYNOPSIS
        Validates SID format and checks for well-known SID exclusions
 
    .DESCRIPTION
        Performs comprehensive SID validation including format checking and
        well-known SID exclusion. This function consolidates SID validation
        logic to ensure consistent handling across the module.
 
    .PARAMETER SIDString
        The SID string to validate
 
    .PARAMETER CorrelationId
        Unique identifier for tracking this operation
 
    .OUTPUTS
        [bool] True if SID is valid and should be processed, False if invalid
        or should be excluded (well-known SID)
 
    .NOTES
        VALIDATION PROCESS:
        1. Check SID format validity using Test-SIDFormat
        2. Check against well-known SID exclusions
        3. Return combined validation result
 
        EXCLUSION CRITERIA:
        - Invalid SID formats
        - Well-known SIDs (system accounts, built-in groups)
        - Empty or null SID strings
    #>


    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory)]
        [string]$SIDString,

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

    try {
        # Validate SID format
        if (-not (Test-SIDFormat -SID $SIDString)) {
            Write-StructuredLog "Invalid SID format: $SIDString" -Level Debug -CorrelationId $CorrelationId
            return $false
        }

        # Check for well-known SIDs (should be excluded from orphaned SID detection)
        if (Test-WellKnownSID -SID $SIDString) {
            Write-StructuredLog "Skipping well-known SID: $SIDString" -Level Debug -CorrelationId $CorrelationId
            return $false
        }

        Write-StructuredLog "SID $SIDString passed validation and is not well-known" -Level Debug -CorrelationId $CorrelationId
        return $true
    }
    catch {
        Write-StructuredLog "Error validating SID $SIDString : $($_.Exception.Message)" -Level Warning -CorrelationId $CorrelationId
        return $false
    }
}

function Get-StringFromIdentityReference {
    <#
    .SYNOPSIS
        Extracts string values from various identity reference types
 
    .DESCRIPTION
        Handles the extraction of string values from different identity reference
        formats, including deserialized objects from background jobs and other
        complex scenarios. This function provides robust string extraction with
        multiple fallback methods.
 
    .PARAMETER IdentityReference
        The identity reference object to process
 
    .PARAMETER ObjectDN
        Distinguished name for context in error logging
 
    .PARAMETER CorrelationId
        Unique identifier for tracking this operation
 
    .OUTPUTS
        [string] Extracted string value or null if unable to extract
 
    .NOTES
        EXTRACTION METHODS:
        1. Value property access (preferred)
        2. ToString() method call
        3. Direct string conversion (fallback)
 
        ERROR HANDLING:
        - Graceful handling of missing properties
        - Safe method invocation with error catching
        - Comprehensive logging for troubleshooting
    #>


    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [PSObject]$IdentityReference,

        [Parameter()]
        [string]$ObjectDN,

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

    try {
        Write-StructuredLog "Extracting string from identity reference (Object: $ObjectDN)" -Level Debug -CorrelationId $CorrelationId
        $stringValue = $null

        # Method 1: Try Value property
        if ($IdentityReference -and $IdentityReference.PSObject.Properties['Value'] -and $IdentityReference.Value) {
            $stringValue = $IdentityReference.Value
            Write-StructuredLog "Extracted string from Value property: $stringValue" -Level Debug -CorrelationId $CorrelationId
        }
        # Method 2: Try ToString method
        elseif ($IdentityReference -and $IdentityReference.ToString) {
            try {
                $stringValue = $IdentityReference.ToString()
                Write-StructuredLog "Extracted string from ToString method: $stringValue" -Level Debug -CorrelationId $CorrelationId
            } catch {
                Write-StructuredLog "Failed to convert identity reference to string using ToString for $ObjectDN : $($_.Exception.Message)" -Level Debug -CorrelationId $CorrelationId
            }
        }
        # Method 3: Direct string conversion (last resort)
        else {
            try {
                $stringValue = [string]$IdentityReference
                Write-StructuredLog "Extracted string using direct conversion: $stringValue" -Level Debug -CorrelationId $CorrelationId
            } catch {
                Write-StructuredLog "Unable to extract string value from identity reference for $ObjectDN : $($_.Exception.Message)" -Level Debug -CorrelationId $CorrelationId
                return $null
            }
        }

        # Validate the extracted string value
        if ([string]::IsNullOrWhiteSpace($stringValue)) {
            Write-StructuredLog "Empty or null string value extracted from identity reference for $ObjectDN" -Level Debug -CorrelationId $CorrelationId
            return $null
        }

        return $stringValue.Trim()
    }
    catch {
        Write-StructuredLog "Error extracting string from identity reference for $ObjectDN : $($_.Exception.Message)" -Level Warning -CorrelationId $CorrelationId
        return $null
    }
}

#endregion