Private/SID/Test-OrphanedSID.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS SID Orphaned Detection and Cache Management Module for Find-UnknownSID Solution .DESCRIPTION This module provides comprehensive SID orphaned detection and cache management functionality for the Find-UnknownSID enterprise solution. It focuses exclusively on identifying orphaned SIDs and managing validation cache for performance. This module follows the single-responsibility principle by focusing exclusively on orphaned SID detection and cache management. It depends on the Test-SIDFormat module for basic validation before performing Active Directory lookups. BUSINESS VALUE: - Enables accurate identification of orphaned SIDs for security cleanup - Reduces false positives through comprehensive validation methods - Improves security posture by identifying unused access rights - Supports compliance requirements for access review and cleanup TECHNICAL FEATURES: - Multiple validation methods (Get-ADObject, SecurityIdentifier translation) - Performance caching to avoid repeated AD queries - Handles deleted objects and various object types - Comprehensive error handling with correlation tracking - Cache management for memory optimization .NOTES Author: Jeffrey Stuhr Blog: https://www.techbyjeff.net LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/ Last Updated: 2025-07-04 Version: 1.0.0 PowerShell Version: 5.1+ (Windows), 7.x+ (Cross-platform) MODULE DESIGN: This module implements the single-responsibility principle by focusing exclusively on orphaned SID detection and cache management. It provides comprehensive Active Directory integration while maintaining high cohesion. TROUBLESHOOTING: - For orphaned detection issues: .\Troubleshooting\Security\Orphaned-SID-Issues.md - For cache management problems: .\Troubleshooting\Common\Cache-Management.md - For performance optimization: .\Troubleshooting\Performance\SID-Detection-Performance.md DEPENDENCIES: - Requires Test-SIDFormat.ps1 for SID format validation - Requires Logging.ps1 for Write-StructuredLog function - Requires ADRetry.ps1 for Invoke-ADOperationWithRetry function - Uses script-scoped $script:SIDValidationCache variable for caching #> # Initialize cache at module level if (-not $script:SIDValidationCache) { $script:SIDValidationCache = @{} } # Lookup counters feed CacheHitRatio in Get-SIDValidationCacheStat if ($null -eq $script:SIDValidationCacheLookups) { $script:SIDValidationCacheLookups = 0 $script:SIDValidationCacheHits = 0 } function Test-OrphanedSID { <# .SYNOPSIS Tests whether a SID is orphaned (no longer exists in Active Directory) .DESCRIPTION Performs comprehensive testing to determine if a SID is orphaned by checking multiple AD lookup methods and utilizing caching for performance. This function is essential for identifying SIDs that should be removed from ACLs during security cleanup operations. BUSINESS VALUE: - Enables accurate identification of orphaned SIDs for security cleanup - Reduces false positives through comprehensive validation methods - Improves security posture by identifying unused access rights - Supports compliance requirements for access review and cleanup TECHNICAL FEATURES: - Multiple validation methods (Get-ADObject, SecurityIdentifier translation, type-specific lookups) - Performance caching to avoid repeated AD queries - Handles deleted objects and various object types - Comprehensive error handling with correlation tracking - Integration with retry logic for AD operations .PARAMETER SID [String] (Mandatory: Yes, Pipeline: ByValue) The Security Identifier to test for orphaned status. VALIDATION RULES: - Must be a valid SID string format - Supports domain SIDs, well-known SIDs, and service SIDs BUSINESS CONTEXT: SIDs from ACL entries that may no longer have corresponding AD objects. Used during security audits and cleanup operations. EXAMPLES: - Domain user: "S-1-5-21-1234567890-1234567890-1234567890-1001" - Built-in account: "S-1-5-32-544" - Service SID: "S-1-5-80-123456789-123456789-123456789-123456789-123456789" .PARAMETER CorrelationId [String] (Mandatory: No) Correlation ID for tracking this operation across logs and troubleshooting. Defaults to auto-generated GUID if not provided. .EXAMPLE PS> Test-OrphanedSID -SID "S-1-5-21-1234567890-1234567890-1234567890-1001" DESCRIPTION: Basic orphaned SID test for domain user OUTPUT: $true if SID is orphaned, $false if valid USE CASE: Security audit to identify unused ACL entries .EXAMPLE PS> @('S-1-5-21-123-456-789-1001', 'S-1-5-32-544') | Test-OrphanedSID DESCRIPTION: Pipeline processing of multiple SIDs OUTPUT: Boolean results for each SID USE CASE: Bulk validation during ACL cleanup operations .EXAMPLE PS> Test-OrphanedSID -SID "S-1-5-21-123-456-789-1001" -CorrelationId $correlationId DESCRIPTION: Orphaned test with correlation tracking OUTPUT: Boolean result with correlated logging USE CASE: Enterprise audit operations requiring full traceability .INPUTS [String] - SID string values via pipeline .OUTPUTS [Boolean] - $true if SID is orphaned, $false if valid and exists in AD .NOTES Author: Jeffrey Stuhr Blog: https://www.techbyjeff.net LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/ Last Updated: 2025-07-04 Version: 1.0.0 PERFORMANCE CHARACTERISTICS: - Uses caching to improve performance for repeated queries - Multiple fallback methods ensure comprehensive validation - Typical execution time: 50-200ms for uncached, <5ms for cached TROUBLESHOOTING: - For validation errors: .\Troubleshooting\Security\Orphaned-SID-Issues.md - For performance issues: .\Troubleshooting\Performance\SID-Detection-Performance.md - For cache management: .\Troubleshooting\Common\Cache-Management.md .LINK Test-SIDFormat Clear-SIDValidationCache Get-SIDValidationCacheStat .\Troubleshooting\Security\Orphaned-SID-Issues.md #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [string]$SID, [Parameter()] [string]$CorrelationId = [System.Guid]::NewGuid().ToString() ) begin { # Initialize cache if it doesn't exist if (-not $script:SIDValidationCache) { $script:SIDValidationCache = @{} } } process { try { Write-StructuredLog "Testing SID for orphaned status: $SID" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId # Validate SID format first using Test-SIDFormat module if (-not (Test-SIDFormat -SID $SID -CorrelationId $CorrelationId)) { Write-StructuredLog "SID format invalid - treating as orphaned: $SID" -Level Warning -Component 'SIDDetection' -CorrelationId $CorrelationId return $true # Invalid SIDs are considered orphaned } # Check cache first for performance $script:SIDValidationCacheLookups++ if ($script:SIDValidationCache.ContainsKey($SID)) { $script:SIDValidationCacheHits++ Write-StructuredLog "SID validation cache hit for $SID" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId return $script:SIDValidationCache[$SID] } # Method 1: Try Get-ADObject with SID (most reliable) try { $adObject = Invoke-ADOperationWithRetry -ScriptBlock { Get-ADObject -Filter "objectSid -eq '$SID'" -ErrorAction Stop } -OperationName 'Get-ADObject-BySID' -ObjectContext $SID -CorrelationId $CorrelationId if ($adObject) { Write-StructuredLog "SID found in AD via Get-ADObject: $($adObject.DistinguishedName)" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId $script:SIDValidationCache[$SID] = $false return $false # Not orphaned } } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { Write-StructuredLog "SID not found via Get-ADObject: $SID" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId } catch { Write-StructuredLog "Get-ADObject lookup failed for SID $SID : $($_.Exception.Message)" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId } # Method 1a: Check for deleted objects specifically try { $deletedObject = Invoke-ADOperationWithRetry -ScriptBlock { Get-ADObject -Filter "objectSid -eq '$SID'" -IncludeDeletedObjects -ErrorAction Stop } -OperationName 'Get-ADObject-Deleted-BySID' -ObjectContext $SID -CorrelationId $CorrelationId if ($deletedObject) { Write-StructuredLog "SID found in deleted objects: $($deletedObject.DistinguishedName) - treating as orphaned" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId $script:SIDValidationCache[$SID] = $true return $true # Treat deleted objects as orphaned for ACL cleanup } } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { Write-StructuredLog "SID not found in deleted objects: $SID" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId } catch { Write-StructuredLog "Deleted objects lookup failed for SID $SID : $($_.Exception.Message)" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId } # Method 2: Try direct SecurityIdentifier translation try { $securityIdentifier = [System.Security.Principal.SecurityIdentifier]::new($SID) $ntAccount = $securityIdentifier.Translate([System.Security.Principal.NTAccount]) if ($ntAccount -and $ntAccount.Value -and -not $ntAccount.Value.StartsWith('S-1-')) { Write-StructuredLog "SID successfully translated to account name: $($ntAccount.Value)" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId $script:SIDValidationCache[$SID] = $false return $false # Not orphaned } } catch [System.Security.Principal.IdentityNotMappedException] { Write-StructuredLog "SID could not be translated - likely orphaned: $SID" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId } catch { Write-StructuredLog "SecurityIdentifier translation failed for $SID : $($_.Exception.Message)" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId } # Method 3: Try specific object type lookups $objectTypes = @( @{ CmdLet = 'Get-ADUser'; Filter = "SID -eq '$SID'" }, @{ CmdLet = 'Get-ADGroup'; Filter = "SID -eq '$SID'" }, @{ CmdLet = 'Get-ADComputer'; Filter = "SID -eq '$SID'" } ) foreach ($objectType in $objectTypes) { try { $result = Invoke-ADOperationWithRetry -ScriptBlock { & $objectType.CmdLet -Filter $objectType.Filter -ErrorAction Stop } -OperationName $objectType.CmdLet -ObjectContext $SID -CorrelationId $CorrelationId if ($result) { Write-StructuredLog "SID found via $($objectType.CmdLet): $($result.DistinguishedName)" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId $script:SIDValidationCache[$SID] = $false return $false # Not orphaned } } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { # Expected for orphaned SIDs, continue to next type continue } catch { Write-StructuredLog "$($objectType.CmdLet) lookup failed for $SID : $($_.Exception.Message)" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId } } # If all methods fail, consider it orphaned Write-StructuredLog "SID could not be resolved - marking as orphaned: $SID" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId $script:SIDValidationCache[$SID] = $true return $true # Orphaned } catch { Write-StructuredLog "Error testing SID for orphaned status $SID : $($_.Exception.Message)" -Level Error -Component 'SIDDetection' -CorrelationId $CorrelationId # Assume orphaned on error for safety $script:SIDValidationCache[$SID] = $true return $true } } } function Clear-SIDValidationCache { <# .SYNOPSIS Clears the SID validation cache to free memory and reset validation state .DESCRIPTION Removes all entries from the SID validation cache, which is used to store results of orphaned SID tests for performance optimization. This function is useful for freeing memory or forcing re-validation of SIDs. BUSINESS VALUE: - Manages memory usage during long-running operations - Allows fresh validation when AD state may have changed - Supports testing and troubleshooting scenarios - Enables cleanup after batch operations TECHNICAL FEATURES: - Safe cache clearing with null checking - Verbose output showing entries cleared - No side effects on other operations .EXAMPLE PS> Clear-SIDValidationCache DESCRIPTION: Clear all cached SID validation results OUTPUT: Verbose message showing entries cleared USE CASE: Memory management during large operations .EXAMPLE PS> Clear-SIDValidationCache; Test-OrphanedSID -SID $sid DESCRIPTION: Force fresh validation by clearing cache first OUTPUT: Cache cleared, then fresh SID validation USE CASE: Troubleshooting when cached results may be stale .INPUTS None .OUTPUTS None (cache clearing operation) .NOTES Author: Jeffrey Stuhr Blog: https://www.techbyjeff.net LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/ Last Updated: 2025-07-04 Version: 1.0.0 PERFORMANCE CHARACTERISTICS: - Instant operation regardless of cache size - No impact on other module functions TROUBLESHOOTING: - For cache issues: .\Troubleshooting\Common\Cache-Management.md - For memory problems: .\Troubleshooting\Performance\Memory-Usage.md .LINK Get-SIDValidationCacheStat Test-OrphanedSID .\Troubleshooting\Common\Cache-Management.md #> [CmdletBinding()] param( [Parameter()] [string]$CorrelationId = [System.Guid]::NewGuid().ToString() ) if ($script:SIDValidationCache) { $count = $script:SIDValidationCache.Count $script:SIDValidationCache.Clear() $script:SIDValidationCacheLookups = 0 $script:SIDValidationCacheHits = 0 Write-StructuredLog "Cleared SID validation cache ($count entries)" -Level Information -Component 'SIDDetection' -CorrelationId $CorrelationId } else { Write-StructuredLog "SID validation cache was already empty" -Level Debug -Component 'SIDDetection' -CorrelationId $CorrelationId } } function Get-SIDValidationCacheStat { <# .SYNOPSIS Retrieves statistics about the current SID validation cache .DESCRIPTION Provides detailed statistics about the SID validation cache including total entries, orphaned SIDs count, and valid SIDs count. This function is useful for monitoring cache utilization and performance analysis. BUSINESS VALUE: - Enables performance monitoring and optimization - Provides visibility into validation patterns - Supports capacity planning for large operations - Assists with troubleshooting cache-related issues TECHNICAL FEATURES: - Safe statistics gathering with null checking - Detailed breakdown of cache contents - No impact on cache performance .EXAMPLE PS> Get-SIDValidationCacheStat DESCRIPTION: Get current cache statistics OUTPUT: Object with TotalEntries, OrphanedSIDs, ValidSIDs counts USE CASE: Performance monitoring during operations .EXAMPLE PS> $stats = Get-SIDValidationCacheStat; Write-Host "Cache efficiency: $($stats.ValidSIDs/$stats.TotalEntries*100)%" DESCRIPTION: Calculate cache efficiency ratio OUTPUT: Cache hit efficiency percentage USE CASE: Performance analysis and optimization .INPUTS None .OUTPUTS [PSCustomObject] with properties: - TotalEntries: Total number of cached SID validations - OrphanedSIDs: Number of SIDs identified as orphaned - ValidSIDs: Number of SIDs identified as valid - CacheHitRatio: Percentage of cache utilization .NOTES Author: Jeffrey Stuhr Blog: https://www.techbyjeff.net LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/ Last Updated: 2025-07-04 Version: 1.0.0 PERFORMANCE CHARACTERISTICS: - Very fast operation (simple counting) - No impact on cache contents or performance TROUBLESHOOTING: - For cache analysis: .\Troubleshooting\Common\Cache-Management.md - For performance tuning: .\Troubleshooting\Performance\Optimization-Guide.md .LINK Clear-SIDValidationCache Test-OrphanedSID .\Troubleshooting\Common\Cache-Management.md #> [CmdletBinding()] [OutputType([PSCustomObject])] param() if ($script:SIDValidationCache) { $totalEntries = $script:SIDValidationCache.Count $orphanedCount = ($script:SIDValidationCache.Values | Where-Object { $_ -eq $true }).Count $validCount = ($script:SIDValidationCache.Values | Where-Object { $_ -eq $false }).Count return [PSCustomObject]@{ PSTypeName = 'SIDValidationCacheStats' TotalEntries = $totalEntries OrphanedSIDs = $orphanedCount ValidSIDs = $validCount CacheHitRatio = if ($script:SIDValidationCacheLookups -gt 0) { [Math]::Round($script:SIDValidationCacheHits / $script:SIDValidationCacheLookups * 100, 2) } else { 0 } LastUpdated = Get-Date } } else { return [PSCustomObject]@{ PSTypeName = 'SIDValidationCacheStats' TotalEntries = 0 OrphanedSIDs = 0 ValidSIDs = 0 CacheHitRatio = 0 LastUpdated = Get-Date } } } |