Private/SID/New-SIDResult.ps1

#Requires -Version 5.1

<#
.SYNOPSIS
    SID Result Factory Module for Find-UnknownSID Solution
 
.DESCRIPTION
    This focused module handles all result object creation and metadata management
    for the Find-UnknownSID enterprise solution. It provides comprehensive factory
    functions for creating standardized result objects with rich metadata and
    proper validation.
 
    SINGLE RESPONSIBILITY:
    This module does ONE thing well: Create and populate result objects with
    comprehensive metadata, ensuring consistent structure and complete information
    for all orphaned SID detection results.
 
    BUSINESS VALUE:
    - Standardized result object creation with consistent metadata
    - Comprehensive error handling and validation for result objects
    - Rich metadata population for audit trails and compliance
    - Extensible factory pattern for future result types
 
    TECHNICAL FEATURES:
    - Safe property extraction from access rules with null handling
    - Comprehensive metadata population including correlation tracking
    - Standardized result object validation and structure
    - Memory-efficient object creation with proper cleanup
    - Integration with enterprise logging and audit systems
 
.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 result creation issues: .\Troubleshooting\Common\Result-Creation-Issues.md
    - For metadata population errors: .\Troubleshooting\Common\Metadata-Population-Issues.md
    - For object validation problems: .\Troubleshooting\Common\Result-Validation-Issues.md
 
    DEPENDENCIES:
    - Requires Classes.ps1 for OrphanedSIDResult class definition
    - Requires Logging.ps1 for Write-StructuredLog function
    - Uses script-scoped variables (CorrelationId for tracking)
#>


#region SID Result Factory Functions

function New-OrphanedSIDResult {
    <#
    .SYNOPSIS
        Creates comprehensive OrphanedSIDResult objects with full metadata
 
    .DESCRIPTION
        Factory function that creates properly structured OrphanedSIDResult objects
        with all required properties, comprehensive metadata, and validation. This
        function ensures consistent result object creation across the entire solution.
 
        The function provides:
        - Complete property population with safe access rule extraction
        - Rich metadata including correlation IDs and processing timestamps
        - Comprehensive validation of input parameters and created objects
        - Support for different processing methods and analysis confidence levels
        - Integration with enterprise logging and audit systems
 
    .PARAMETER ObjectDN
        Distinguished name of the Active Directory object containing the orphaned SID.
        This provides context for where the orphaned SID was discovered.
 
    .PARAMETER ObjectClass
        Class of the AD object (user, group, computer, organizationalUnit, etc.)
        for categorization and analysis context.
 
    .PARAMETER OrphanedSID
        The orphaned Security Identifier string that was detected.
        Must be a valid SID format.
 
    .PARAMETER AccessRule
        The access rule object containing the orphaned SID. Used to extract
        detailed ACL information including rights, inheritance, and object types.
 
    .PARAMETER LikelySource
        Categorized source of the orphaned SID based on analysis.
        Examples: "Deleted User", "Moved Account", "Unknown Domain", etc.
 
    .PARAMETER Confidence
        Analysis confidence level indicating the reliability of the source categorization.
        Values: "High", "Medium", "Low"
 
    .PARAMETER Notes
        Detailed analysis notes providing additional context and recommendations
        for the orphaned SID.
 
    .PARAMETER ProcessingMethod
        Method used to detect the orphaned SID for audit and troubleshooting purposes.
        Examples: "Enhanced-ACL-Retrieval", "Identity-Resolution", "NTAccount-Translation-Failed"
 
    .PARAMETER CorrelationId
        Unique identifier for tracking this operation across logs and audit trails.
        Generated automatically if not provided.
 
    .EXAMPLE
        PS> $result = New-OrphanedSIDResult -ObjectDN $dn -OrphanedSID $sid -AccessRule $rule
 
        DESCRIPTION: Basic orphaned SID result creation with minimal parameters
        OUTPUT: Complete OrphanedSIDResult object with auto-generated metadata
        USE CASE: Standard orphaned SID result creation in processing workflows
 
    .EXAMPLE
        PS> $result = New-OrphanedSIDResult -ObjectDN $dn -ObjectClass "User" -OrphanedSID $sid -AccessRule $rule -LikelySource "Deleted User" -Confidence "High" -Notes "Account was deleted 30 days ago"
 
        DESCRIPTION: Comprehensive result creation with full analysis metadata
        OUTPUT: Rich OrphanedSIDResult object with detailed analysis information
        BUSINESS CASE: Complete audit trail with high-confidence analysis
 
    .EXAMPLE
        PS> $result = New-OrphanedSIDResult -ObjectDN $dn -OrphanedSID $sid -AccessRule $rule -ProcessingMethod "NTAccount-Translation-Failed" -CorrelationId $correlationId
 
        DESCRIPTION: Result creation with specific processing method and correlation tracking
        OUTPUT: OrphanedSIDResult with audit trail correlation and processing context
        INTEGRATION: Enterprise environments requiring detailed tracking and compliance
 
    .OUTPUTS
        [OrphanedSIDResult] Fully populated result object containing:
        - Object identification (DN, class, timestamp)
        - Orphaned SID details (SID, source analysis, confidence)
        - ACL information (access control type, rights, inheritance)
        - Processing metadata (correlation ID, method, timestamp)
        - Analysis details (notes, recommendations, confidence level)
 
    .NOTES
        OBJECT CREATION PROCESS:
        1. Validate input parameters and SID format
        2. Create new OrphanedSIDResult object instance
        3. Populate core object identification properties
        4. Extract and set ACL metadata safely
        5. Add comprehensive processing metadata
        6. Validate completed object structure
 
        METADATA CATEGORIES:
        - Object Context: DN, class, discovery timestamp
        - SID Analysis: Orphaned SID, source, confidence level
        - ACL Details: Access rights, control type, inheritance
        - Processing Info: Method, correlation ID, action taken
        - Audit Trail: Timestamps, operator context, compliance data
 
        ERROR HANDLING:
        - Comprehensive input validation with detailed error messages
        - Safe property extraction with null checking
        - Graceful handling of missing or invalid access rule properties
        - Detailed logging for troubleshooting and audit purposes
 
        PERFORMANCE CHARACTERISTICS:
        - Efficient object creation with minimal memory allocation
        - Safe property access with error containment
        - Optimized for high-volume processing scenarios
        - Memory cleanup considerations for large result sets
    #>


    [CmdletBinding()]
    [OutputType([OrphanedSIDResult])]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'This is a factory function that creates objects but does not change system state')]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ObjectDN,

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

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

        [Parameter(Mandatory)]
        [PSObject]$AccessRule,

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

        [Parameter()]
        [ValidateSet("High", "Medium", "Low")]
        [string]$Confidence = "Medium",

        [Parameter()]
        [string]$Notes = "",

        [Parameter()]
        [string]$ProcessingMethod = "Standard",

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

    try {
        Write-StructuredLog "Creating OrphanedSIDResult object for $ObjectDN" -Level Debug -CorrelationId $CorrelationId

        # Validate OrphanedSID format
        if (-not (Test-SIDFormat -SID $OrphanedSID)) {
            throw "Invalid SID format provided: $OrphanedSID"
        }

        # Create new result object instance
        $result = [OrphanedSIDResult]::new()

        # Set core object identification properties
        $result.ObjectDN = $ObjectDN.Trim()
        $result.ObjectClass = $ObjectClass
        $result.OrphanedSID = $OrphanedSID
        $result.LikelySource = $LikelySource
        $result.Confidence = $Confidence
        $result.AnalysisNotes = $Notes

        # Add processing metadata
        Add-ProcessingMetadata -Result $result -ProcessingMethod $ProcessingMethod -CorrelationId $CorrelationId

        # Extract and set ACL metadata safely
        Set-ACLMetadata -Result $result -AccessRule $AccessRule -CorrelationId $CorrelationId

        Write-StructuredLog "Successfully created OrphanedSIDResult object for $ObjectDN with SID $OrphanedSID" -Level Debug -CorrelationId $CorrelationId

        return $result
    }
    catch {
        Write-StructuredLog "Error creating OrphanedSIDResult object for $ObjectDN : $($_.Exception.Message)" -Level Error -CorrelationId $CorrelationId
        throw "Failed to create OrphanedSIDResult: $($_.Exception.Message)"
    }
}

function Add-ProcessingMetadata {
    <#
    .SYNOPSIS
        Adds comprehensive processing metadata to result objects
 
    .DESCRIPTION
        Populates result objects with detailed processing metadata including
        correlation IDs, timestamps, processing methods, and audit information.
        This function ensures consistent metadata across all result objects.
 
    .PARAMETER Result
        The OrphanedSIDResult object to populate with metadata
 
    .PARAMETER ProcessingMethod
        Method used to detect the orphaned SID
 
    .PARAMETER CorrelationId
        Unique identifier for tracking this operation
 
    .NOTES
        METADATA ADDED:
        - Processing method and timestamp
        - Correlation ID for audit trails
        - Action taken and operator context
        - System and environment information
    #>


    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '',
        Justification = 'Metadata is a mass noun; the name is shared with the tests and docs')]
    param(
        [Parameter(Mandatory)]
        [OrphanedSIDResult]$Result,

        [Parameter()]
        [string]$ProcessingMethod = "Standard",

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

    try {
        Write-StructuredLog "Adding processing metadata to result object" -Level Debug -CorrelationId $CorrelationId

        # Core processing metadata
        $Result.ProcessingMethod = $ProcessingMethod
        $Result.ActionTaken = "Detected"
        $Result.CorrelationId = $CorrelationId
        $Result.Timestamp = Get-Date

        # Additional context metadata
        if (-not $Result.PSObject.Properties['ProcessingContext']) {
            $Result | Add-Member -NotePropertyName 'ProcessingContext' -NotePropertyValue @{
                MachineName = $env:COMPUTERNAME
                UserName = $env:USERNAME
                ProcessId = $PID
                PowerShellVersion = $PSVersionTable.PSVersion.ToString()
                ModuleVersion = "1.0.0"
            }
        }

        Write-StructuredLog "Successfully added processing metadata with correlation ID $CorrelationId" -Level Debug -CorrelationId $CorrelationId
    }
    catch {
        Write-StructuredLog "Error adding processing metadata: $($_.Exception.Message)" -Level Warning -CorrelationId $CorrelationId
        throw
    }
}

function Set-ACLMetadata {
    <#
    .SYNOPSIS
        Safely extracts and sets ACL metadata from access rules
 
    .DESCRIPTION
        Performs safe extraction of ACL properties from access rule objects,
        handling missing properties and null values gracefully. This function
        ensures comprehensive ACL metadata is captured for audit and analysis.
 
    .PARAMETER Result
        The OrphanedSIDResult object to populate with ACL metadata
 
    .PARAMETER AccessRule
        The access rule object to extract metadata from
 
    .PARAMETER CorrelationId
        Unique identifier for tracking this operation
 
    .NOTES
        ACL METADATA EXTRACTED:
        - Access control type (Allow/Deny)
        - Active Directory rights and permissions
        - Inheritance flags and object types
        - Object GUIDs for extended rights
        - Inheritance hierarchy information
 
        ERROR HANDLING:
        - Safe property access with null checking
        - Graceful handling of missing properties
        - Default values for unavailable metadata
        - Comprehensive logging for troubleshooting
    #>


    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '',
        Justification = 'Metadata is a mass noun; the name is shared with the tests and docs')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Populates properties on the result object passed in, nothing on the system')]
    param(
        [Parameter(Mandatory)]
        [OrphanedSIDResult]$Result,

        [Parameter(Mandatory)]
        [PSObject]$AccessRule,

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

    try {
        Write-StructuredLog "Extracting ACL metadata from access rule" -Level Debug -CorrelationId $CorrelationId

        # Extract access control type safely
        if ($AccessRule.PSObject.Properties['AccessControlType']) {
            $Result.AccessControlType = $AccessRule.AccessControlType
            Write-StructuredLog "Set AccessControlType: $($Result.AccessControlType)" -Level Debug -CorrelationId $CorrelationId
        } else {
            $Result.AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow
            Write-StructuredLog "AccessControlType property missing, set to Allow" -Level Debug -CorrelationId $CorrelationId
        }

        # Extract Active Directory rights safely
        if ($AccessRule.PSObject.Properties['ActiveDirectoryRights']) {
            $Result.ActiveDirectoryRights = $AccessRule.ActiveDirectoryRights
            Write-StructuredLog "Set ActiveDirectoryRights: $($Result.ActiveDirectoryRights)" -Level Debug -CorrelationId $CorrelationId
        } else {
            # Try alternative property names
            if ($AccessRule.PSObject.Properties['Rights']) {
                $Result.ActiveDirectoryRights = $AccessRule.Rights
                Write-StructuredLog "Set ActiveDirectoryRights from Rights property: $($Result.ActiveDirectoryRights)" -Level Debug -CorrelationId $CorrelationId
            } else {
                $Result.ActiveDirectoryRights = [System.DirectoryServices.ActiveDirectoryRights]::GenericRead
                Write-StructuredLog "ActiveDirectoryRights property missing, set to GenericRead" -Level Debug -CorrelationId $CorrelationId
            }
        }

        # Extract inheritance information safely
        if ($AccessRule.PSObject.Properties['InheritanceFlags']) {
            $Result.InheritanceType = $AccessRule.InheritanceFlags
            Write-StructuredLog "Set InheritanceType: $($Result.InheritanceType)" -Level Debug -CorrelationId $CorrelationId
        } else {
            $Result.InheritanceType = [System.Security.AccessControl.InheritanceFlags]::None
            Write-StructuredLog "InheritanceFlags property missing, set to None" -Level Debug -CorrelationId $CorrelationId
        }

        # Handle ObjectType GUID safely
        $Result.ObjectType = Get-SafeGuidProperty -Object $AccessRule -PropertyName 'ObjectType' -CorrelationId $CorrelationId

        # Handle InheritedObjectType GUID safely
        $Result.InheritedObjectType = Get-SafeGuidProperty -Object $AccessRule -PropertyName 'InheritedObjectType' -CorrelationId $CorrelationId

        # Extract inheritance status safely
        if ($AccessRule.PSObject.Properties['IsInherited']) {
            $Result.IsInherited = $AccessRule.IsInherited
            Write-StructuredLog "Set IsInherited: $($Result.IsInherited)" -Level Debug -CorrelationId $CorrelationId
        } else {
            $Result.IsInherited = $false
            Write-StructuredLog "IsInherited property missing, set to false" -Level Debug -CorrelationId $CorrelationId
        }

        Write-StructuredLog "Successfully extracted all available ACL metadata" -Level Debug -CorrelationId $CorrelationId
    }
    catch {
        Write-StructuredLog "Error extracting ACL metadata: $($_.Exception.Message)" -Level Warning -CorrelationId $CorrelationId
        throw
    }
}

function Get-SafeGuidProperty {
    <#
    .SYNOPSIS
        Safely extracts GUID properties from objects with proper null handling
 
    .DESCRIPTION
        Handles the safe extraction of GUID properties from various object types,
        including proper handling of null values, empty GUIDs, and missing properties.
 
    .PARAMETER Object
        The object to extract the GUID property from
 
    .PARAMETER PropertyName
        Name of the GUID property to extract
 
    .PARAMETER CorrelationId
        Unique identifier for tracking this operation
 
    .OUTPUTS
        [System.Guid] The extracted GUID or Empty GUID if not available
 
    .NOTES
        GUID HANDLING:
        - Returns actual GUID if property exists and has value
        - Returns Empty GUID for null or missing properties
        - Handles various GUID string formats
        - Provides comprehensive error handling
    #>


    [CmdletBinding()]
    [OutputType([System.Guid])]
    param(
        [Parameter(Mandatory)]
        [PSObject]$Object,

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

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

    try {
        if ($Object.PSObject.Properties[$PropertyName] -and $Object.$PropertyName) {
            $guidValue = $Object.$PropertyName

            # Handle different GUID formats
            if ($guidValue -is [System.Guid]) {
                if ($guidValue -eq [System.Guid]::Empty) {
                    Write-StructuredLog "GUID property $PropertyName is empty GUID" -Level Debug -CorrelationId $CorrelationId
                    return [System.Guid]::Empty
                } else {
                    Write-StructuredLog "Set $PropertyName to GUID: $guidValue" -Level Debug -CorrelationId $CorrelationId
                    return $guidValue
                }
            } elseif ($guidValue -is [string]) {
                if ([string]::IsNullOrWhiteSpace($guidValue) -or $guidValue -eq "00000000-0000-0000-0000-000000000000") {
                    Write-StructuredLog "GUID property $PropertyName is empty string or zero GUID" -Level Debug -CorrelationId $CorrelationId
                    return [System.Guid]::Empty
                } else {
                    try {
                        $parsedGuid = [System.Guid]::Parse($guidValue)
                        Write-StructuredLog "Set $PropertyName to parsed GUID: $parsedGuid" -Level Debug -CorrelationId $CorrelationId
                        return $parsedGuid
                    } catch {
                        Write-StructuredLog "Failed to parse GUID string for $PropertyName : $guidValue" -Level Debug -CorrelationId $CorrelationId
                        return [System.Guid]::Empty
                    }
                }
            } else {
                Write-StructuredLog "GUID property $PropertyName has unexpected type: $($guidValue.GetType().Name)" -Level Debug -CorrelationId $CorrelationId
                return [System.Guid]::Empty
            }
        } else {
            Write-StructuredLog "GUID property $PropertyName missing or null, set to Empty" -Level Debug -CorrelationId $CorrelationId
            return [System.Guid]::Empty
        }
    }
    catch {
        Write-StructuredLog "Error extracting GUID property $PropertyName : $($_.Exception.Message)" -Level Warning -CorrelationId $CorrelationId
        return [System.Guid]::Empty
    }
}

function ConvertTo-ResultSummary {
    <#
    .SYNOPSIS
        Creates summary objects from collections of OrphanedSIDResult objects
 
    .DESCRIPTION
        Generates comprehensive summary reports from collections of orphaned SID
        results, providing aggregate statistics and categorized analysis for
        reporting and dashboard purposes.
 
    .PARAMETER OrphanedSIDResults
        Collection of OrphanedSIDResult objects to summarize
 
    .PARAMETER CorrelationId
        Unique identifier for tracking this operation
 
    .OUTPUTS
        [PSCustomObject] Summary object containing aggregate statistics and analysis
 
    .NOTES
        SUMMARY CATEGORIES:
        - Total counts and percentages
        - Source categorization analysis
        - Confidence level distribution
        - Object class analysis
        - Processing method statistics
        - Temporal analysis and trends
 
        BUSINESS VALUE:
        - Executive reporting capabilities
        - Trend analysis for security improvements
        - Resource allocation guidance
        - Compliance reporting support
    #>


    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [OrphanedSIDResult[]]$OrphanedSIDResults,

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

    begin {
        # Accumulate pipeline input for proper array processing
        $allResults = @()
    }

    process {
        # Add each pipeline input to accumulator
        $allResults += $OrphanedSIDResults
    }

    end {
        try {
            Write-StructuredLog "Creating result summary for $($allResults.Count) orphaned SID results" -Level Debug -CorrelationId $CorrelationId

            $summary = [PSCustomObject]@{
                # Basic statistics
                TotalOrphanedSIDs = $allResults.Count
                UniqueObjects = ($allResults | Select-Object -Property ObjectDN -Unique).Count
                UniqueOrphanedSIDs = ($allResults | Select-Object -Property OrphanedSID -Unique).Count

                # Source analysis
                SourceCategories = $allResults | Group-Object -Property LikelySource | ForEach-Object {
                    [PSCustomObject]@{
                        Source = $_.Name
                        Count = $_.Count
                        Percentage = [Math]::Round(($_.Count / $allResults.Count) * 100, 2)
                    }
                } | Sort-Object -Property Count -Descending

                # Confidence distribution
                ConfidenceDistribution = $allResults | Group-Object -Property Confidence | ForEach-Object {
                    [PSCustomObject]@{
                        Confidence = $_.Name
                        Count = $_.Count
                        Percentage = [Math]::Round(($_.Count / $allResults.Count) * 100, 2)
                    }
                }

                # Object class analysis
                ObjectClassAnalysis = $allResults | Group-Object -Property ObjectClass | ForEach-Object {
                    [PSCustomObject]@{
                        ObjectClass = $_.Name
                        Count = $_.Count
                        Percentage = [Math]::Round(($_.Count / $allResults.Count) * 100, 2)
                    }
                } | Sort-Object -Property Count -Descending

                # Processing method statistics
                ProcessingMethods = $allResults | Group-Object -Property ProcessingMethod | ForEach-Object {
                    [PSCustomObject]@{
                        Method = $_.Name
                        Count = $_.Count
                        Percentage = [Math]::Round(($_.Count / $allResults.Count) * 100, 2)
                    }
                }

                # Temporal analysis
                ProcessingTimespan = @{
                    Earliest = ($allResults | Measure-Object -Property Timestamp -Minimum).Minimum
                    Latest = ($allResults | Measure-Object -Property Timestamp -Maximum).Maximum
                }

                # Access rights analysis
                AccessRightsAnalysis = $allResults | Group-Object -Property ActiveDirectoryRights | ForEach-Object {
                    [PSCustomObject]@{
                        Rights = $_.Name
                        Count = $_.Count
                        Percentage = [Math]::Round(($_.Count / $allResults.Count) * 100, 2)
                    }
                } | Sort-Object -Property Count -Descending | Select-Object -First 10

                # Summary metadata
                SummaryMetadata = @{
                    GeneratedAt = Get-Date
                    CorrelationId = $CorrelationId
                    GeneratedBy = $env:USERNAME
                    MachineName = $env:COMPUTERNAME
                }
            }

            Write-StructuredLog "Successfully created result summary with $($summary.TotalOrphanedSIDs) total orphaned SIDs" -Level Debug -CorrelationId $CorrelationId

            return $summary
        }
        catch {
            Write-StructuredLog "Error creating result summary: $($_.Exception.Message)" -Level Error -CorrelationId $CorrelationId
            throw
        }
    }
}

#endregion