Classes/ScriptConfiguration.ps1

#Requires -Version 5.1

class ScriptConfiguration {
    [string[]]$WellKnownSIDPatterns
    [string[]]$ProtectedSIDs
    [string[]]$CriticalObjectPatterns
    [int]$BatchSize
    [int]$MemoryCheckInterval
    [bool]$EnableDetailedLogging
    [string]$LogLevel
    [string]$LogFilePath
    [bool]$EnableLogFileRotation
    [int]$MaxLogFileSizeMB

    ScriptConfiguration() {
        $this.InitializeDefaults()
    }

    [void]InitializeDefaults() {
        $this.WellKnownSIDPatterns = @(
            '^S-1-1-0$',          # Everyone
            '^S-1-5-11$',         # Authenticated Users
            '^S-1-5-32-',         # Built-in groups
            '^S-1-3-[0-4]$',      # Creator Owner/Group
            '^S-1-5-18$',         # Local System
            '^S-1-5-19$',         # Local Service
            '^S-1-5-20$',         # Network Service
            '^S-1-5-6$',          # Service
            '^S-1-16-',           # Integrity levels
            '^S-1-15-2-\d+$',     # App packages
            '^S-1-5-80-\d+$',     # NT Service
            '^S-1-5-90-\d+$',     # Windows Manager
            '^S-1-5-96-\d+$',     # Font drivers
            '^S-1-5-84-\d+$'      # User-mode drivers
        )

        $this.ProtectedSIDs = @(
            'S-1-5-32-544',    # Administrators
            'S-1-5-32-545',    # Users
            'S-1-5-32-546',    # Guests
            'S-1-5-18',        # Local System
            'S-1-5-19',        # Local Service
            'S-1-5-20'         # Network Service
        )

        $this.CriticalObjectPatterns = @(
            '*CN=Domain Admins*',
            '*CN=Enterprise Admins*',
            '*CN=Schema Admins*',
            '*CN=BUILTIN*',
            '*CN=Users,DC=*',
            '*CN=Computers,DC=*'
        )

        # Results per streaming batch file written by StreamingResultsManager
        $this.BatchSize = 50
        $this.MemoryCheckInterval = 50
        $this.EnableDetailedLogging = $false
        $this.LogLevel = 'Information'
        $this.LogFilePath = $null
        $this.EnableLogFileRotation = $true
        $this.MaxLogFileSizeMB = 10
    }

    static [ScriptConfiguration]LoadFromFile([string]$configPath) {
        if (-not (Test-Path $configPath)) {
            throw "Configuration file not found: $configPath"
        }

        try {
            $configData = Get-Content $configPath -Raw | ConvertFrom-Json
            $config = [ScriptConfiguration]::new()

            # Validation with proper error handling
            if ($configData.WellKnownSIDPatterns) {
                # Validate SID patterns
                foreach ($pattern in $configData.WellKnownSIDPatterns) {
                    try {
                        $null = [regex]::new($pattern)
                    }
                    catch {
                        throw "Invalid regex pattern in WellKnownSIDPatterns: $pattern"
                    }
                }
                $config.WellKnownSIDPatterns = $configData.WellKnownSIDPatterns
            }

            if ($configData.ProtectedSIDs) {
                # Validate SID formats
                foreach ($sid in $configData.ProtectedSIDs) {
                    if (-not ($sid -match '^S-1-\d+(-\d+)*$')) {
                        throw "Invalid SID format in ProtectedSIDs: $sid"
                    }
                }
                $config.ProtectedSIDs = $configData.ProtectedSIDs
            }

            if ($configData.BatchSize) {
                if ($configData.BatchSize -lt 1 -or $configData.BatchSize -gt 1000) {
                    throw "BatchSize must be between 1 and 1000, got: $($configData.BatchSize)"
                }
                $config.BatchSize = $configData.BatchSize
            }

            if ($configData.MemoryCheckInterval) {
                if ($configData.MemoryCheckInterval -lt 1 -or $configData.MemoryCheckInterval -gt 1000) {
                    throw "MemoryCheckInterval must be between 1 and 1000, got: $($configData.MemoryCheckInterval)"
                }
                $config.MemoryCheckInterval = $configData.MemoryCheckInterval
            }

            if ($null -ne $configData.EnableDetailedLogging) { $config.EnableDetailedLogging = $configData.EnableDetailedLogging }
            if ($configData.LogLevel) { $config.LogLevel = $configData.LogLevel }
            if ($configData.LogFilePath) { $config.LogFilePath = $configData.LogFilePath }
            if ($null -ne $configData.EnableLogFileRotation) { $config.EnableLogFileRotation = $configData.EnableLogFileRotation }
            if ($configData.MaxLogFileSizeMB) { $config.MaxLogFileSizeMB = $configData.MaxLogFileSizeMB }

            return $config

        } catch {
            throw "Failed to load configuration from $configPath : $($_.Exception.Message)"
        }
    }

    # Findings are warnings, not errors: the method answers with $false and the caller decides. A
    # Write-Error here becomes terminating under an inherited -ErrorAction Stop, so the caller would
    # see an exception instead of the $false it tests for.

    [bool] ValidateConfiguration() {
        try {
            Write-Verbose "Starting configuration validation"

            # Validate SID patterns with detailed error reporting
            foreach ($pattern in $this.WellKnownSIDPatterns) {
                try {
                    $null = [regex]::new($pattern)
                    Write-Verbose "Validated SID pattern: $pattern"
                } catch {
                    Write-Warning "Invalid regex pattern in WellKnownSIDPatterns: $pattern - $($_.Exception.Message)"
                    return $false
                }
            }

            # Validate protected SIDs format and accessibility
            foreach ($sid in $this.ProtectedSIDs) {
                if (-not ($sid -match '^S-1-\d+(-\d+)*$')) {
                    Write-Warning "Invalid protected SID format: $sid"
                    return $false
                }

                # Additional validation: Ensure it's not a malformed well-known SID
                try {
                    $null = [System.Security.Principal.SecurityIdentifier]::new($sid)
                    Write-Verbose "Validated protected SID: $sid"
                } catch {
                    Write-Warning "Protected SID failed .NET validation: $sid - $($_.Exception.Message)"
                    return $false
                }
            }

            # Validate critical object patterns for proper wildcard usage
            foreach ($pattern in $this.CriticalObjectPatterns) {
                if ([string]::IsNullOrWhiteSpace($pattern)) {
                    Write-Warning "Empty critical object pattern detected"
                    return $false
                }

                # Ensure patterns have reasonable structure
                if ($pattern.Length -lt 3 -or $pattern.Length -gt 500) {
                    Write-Warning "Critical object pattern has invalid length: $pattern"
                    return $false
                }

                Write-Verbose "Validated critical object pattern: $pattern"
            }

            # Validate numeric ranges with business logic
            if ($this.BatchSize -lt 1 -or $this.BatchSize -gt 1000) {
                Write-Warning "BatchSize out of valid range (1-1000): $($this.BatchSize)"
                return $false
            }

            if ($this.MemoryCheckInterval -lt 1 -or $this.MemoryCheckInterval -gt 1000) {
                Write-Warning "MemoryCheckInterval out of valid range (1-1000): $($this.MemoryCheckInterval)"
                return $false
            }

            # Validate log level against known values
            $validLogLevels = @('Critical', 'Error', 'Warning', 'Information', 'Debug', 'Verbose')
            if ($this.LogLevel -notin $validLogLevels) {
                Write-Warning "Invalid LogLevel specified: $($this.LogLevel). Valid values: $($validLogLevels -join ', ')"
                return $false
            }

            Write-Verbose "Configuration validation completed successfully"
            return $true

        } catch {
            Write-Warning "Configuration validation failed: $($_.Exception.Message)"
            return $false
        }
    }
}