Private/Test-ControlBook.ps1

# DLP: Evaluate DLP Compliance Policy property
function Test-GetDlpCompliancePolicyProperty {
    param($Result, $PropertyParts, $ExpectedValue, $OptimizedReport)
    $policies = $OptimizedReport.GetDlpCompliancePolicy
    if (-not $policies) {
        $Result.Comments = "No DLP policies found in report"
        return $Result
    }
    $propertyName = $PropertyParts[1].Trim()
    switch ($propertyName) {
        "Mode" {
            $found = $policies | Where-Object { $_.Mode -eq $ExpectedValue }
            if ($found) {
                $Result.Pass = $true
                $Result.Comments = "Found DLP policy with Mode: $ExpectedValue"
            } else {
                $Result.Comments = "No DLP policy found with Mode: $ExpectedValue"
            }
        }
        default {
            $Result.Comments = "Unknown DLP policy property: $propertyName"
        }
    }
    return $Result
}

# DLP: Evaluate DLP Compliance Rule property
function Test-GetDlpComplianceRuleProperty {
    param($Result, $PropertyParts, $ExpectedValue, $OptimizedReport)
    $rules = $OptimizedReport.GetDlpComplianceRule
    $policies = $OptimizedReport.GetDlpCompliancePolicy
    
    if (-not $rules) {
        $Result.Comments = "No DLP rules found in report"
        return $Result
    }

    # Add state to track which rule we're working with for this control
    if (-not $script:CurrentDlpControlRules) {
        $script:CurrentDlpControlRules = @{}
    }
    $controlId = $Result.ControlID

    # Filter rules to only those from enabled policies
    $enabledRules = @()
    $simulationRules = @()
    
    foreach ($rule in $rules) {
        if ($rule.PSObject.Properties.Name -contains "Policy") {
            $parentPolicy = $policies | Where-Object { $_.Guid -eq $rule.Policy }
            if ($parentPolicy) {
                if ($parentPolicy.Mode -eq "Enable") {
                    $enabledRules += $rule
                } elseif ($parentPolicy.Mode -like "Test*") {
                    $simulationRules += $rule
                }
            }
        }
    }

    $propertyName = $PropertyParts[1].Trim()
    
    # Reconstruct the full property path for compound DLP properties
    # PropertyParts[1] might be "AdvancedRule >> Sensitivetypes" and PropertyParts[2] might be "Mincount"
    if ($PropertyParts.Count -gt 2 -and $propertyName -like "AdvancedRule*") {
        # Rebuild the compound property path
        $additionalParts = @()
        for ($i = 2; $i -lt $PropertyParts.Count; $i++) {
            $additionalParts += $PropertyParts[$i].Trim()
        }
        $propertyName = $propertyName + " > " + ($additionalParts -join " > ")
    }
    
    # Handle AdvancedRule properties with >> syntax
    if ($propertyName -like "AdvancedRule*") {
        # Parse AdvancedRule properties - need to handle compound paths correctly
        # Example: "AdvancedRule >> Sensitivetypes > Mincount" should extract "Sensitivetypes > Mincount"
        if ($propertyName -like "AdvancedRule >> *") {
            # For compound paths, extract everything after "AdvancedRule >> "
            $deepProp = $propertyName -replace '^AdvancedRule >> ', ''
            $deepVal = $ExpectedValue
            $found = $false
            $foundRule = $null
            
            # Check if we already have a matched rule for this control
            if ($script:CurrentDlpControlRules.ContainsKey($controlId)) {
                $previousRule = $script:CurrentDlpControlRules[$controlId]
                if ($previousRule.PSObject.Properties.Name -contains "AdvancedRule" -and $previousRule.AdvancedRule) {
                    try {
                        $adv = $previousRule.AdvancedRule | ConvertFrom-Json -ErrorAction Stop
                        $names = Get-DlpDeepProperty -Node $adv -Target $deepProp
                        if ($deepVal -eq 'Not Null') {
                            if ($names.Count -gt 0) { 
                                $Result.Pass = $true
                                $Result.Comments = "Previously matched DLP rule has $deepProp configured in AdvancedRule"
                                return $Result
                            }
                        } else {
                            if ($names -contains $deepVal) { 
                                $Result.Pass = $true
                                $Result.Comments = "Previously matched DLP rule has $deepProp = $deepVal in AdvancedRule"
                                return $Result
                            }
                        }
                    } catch {
                        # Continue to search other rules
                    }
                }
            }
            
            # Search enabled rules for AdvancedRule content (and disabled rules for specific controls)
            $rulesToSearch = $enabledRules
            if ($controlId -eq "DLP_4.6" -or $controlId -eq "DLP_4.7" -or $controlId -eq "DLP_4.8") {
                # For these specific controls, also search disabled rules since target rule might be disabled
                $rulesToSearch = $rules
            }
            
            foreach ($rule in $rulesToSearch) {
                if ($rule.PSObject.Properties.Name -contains "AdvancedRule" -and $rule.AdvancedRule) {
                    try {
                        $adv = $rule.AdvancedRule | ConvertFrom-Json -ErrorAction Stop
                        
                        # Use enhanced DLP parsing for compound properties (DLP_4.6, 4.7, 4.8)
                        $isCompoundProp = $deepProp -like "*>*"
                        $isTargetControl = ($controlId -eq "DLP_4.6" -or $controlId -eq "DLP_4.7" -or $controlId -eq "DLP_4.8")
                        
                        if ($isCompoundProp -and $isTargetControl) {
                            try {
                                # Load enhanced parser functions
                                . "$PSScriptRoot\DlpAdvancedParser.ps1"
                                $parseResult = Test-DlpAdvancedRuleProperty -AdvRule $adv -DeepProp $deepProp -DeepVal $deepVal -ControlId $controlId
                                if ($parseResult.Found) {
                                    $found = $true
                                    $foundRule = $rule
                                    break
                                }
                            } catch {
                                # Continue to next rule
                            }
                        } else {
                            # Use existing logic for simple properties
                            $names = Get-DlpDeepProperty -Node $adv -Target $deepProp
                            
                            if ($deepVal -eq 'Not Null') {
                                if ($names.Count -gt 0) { 
                                    $found = $true
                                    $foundRule = $rule
                                    break
                                }
                            } elseif ($deepProp -like "*MinCount" -and $deepVal -match '^\d+$') {
                                # Handle numeric MinCount comparison (>= expected value)
                                $targetMinCount = [int]$deepVal
                                $highestMinCount = if ($names.Count -gt 0) { ($names | Measure-Object -Maximum).Maximum } else { 0 }
                                if ($highestMinCount -ge $targetMinCount) {
                                    $found = $true
                                    $foundRule = $rule
                                    break
                                }
                            } elseif ($deepVal -match "," -and $deepProp -like "*Name") {
                                # Handle comma-separated list of required names
                                $requiredNames = $deepVal -split ',' | ForEach-Object { $_.Trim() }
                                $allFound = $true
                                foreach ($reqName in $requiredNames) {
                                    if ($names -notcontains $reqName) {
                                        $allFound = $false
                                        break
                                    }
                                }
                                if ($allFound) {
                                    $found = $true
                                    $foundRule = $rule
                                    break
                                }
                            } else {
                                # Standard string matching
                                if ($names -contains $deepVal) { 
                                    $found = $true
                                    $foundRule = $rule
                                    break
                                }
                            }
                        }
                    } catch {
                        # Continue to next rule
                    }
                }
            }
            
            if ($found) {
                $script:CurrentDlpControlRules[$controlId] = $foundRule
                $Result.Pass = $true
                if ($deepVal -eq 'Not Null') {
                    $Result.Comments = "Found DLP rule with $deepProp configured in AdvancedRule"
                } else {
                    $Result.Comments = "Found DLP rule with $deepProp = $deepVal in AdvancedRule"
                }
            } else {
                # Check simulation rules for informational comments
                $simulationFound = $false
                foreach ($rule in $simulationRules) {
                    if ($rule.PSObject.Properties.Name -contains "AdvancedRule" -and $rule.AdvancedRule) {
                        try {
                            $adv = $rule.AdvancedRule | ConvertFrom-Json -ErrorAction Stop
                            
                            # Use enhanced DLP parsing for compound properties (DLP_4.6, 4.7, 4.8)
                            if ($deepProp -like "*>*" -and ($controlId -eq "DLP_4.6" -or $controlId -eq "DLP_4.7" -or $controlId -eq "DLP_4.8")) {
                                # Load enhanced parser functions (already loaded above)
                                if (Get-Command Test-DlpAdvancedRuleProperty -ErrorAction SilentlyContinue) {
                                    $parseResult = Test-DlpAdvancedRuleProperty -AdvRule $adv -DeepProp $deepProp -DeepVal $deepVal -ControlId $controlId
                                    if ($parseResult.Found) { $simulationFound = $true; break }
                                }
                            } else {
                                # Use existing logic
                                $names = Get-DlpDeepProperty -Node $adv -Target $deepProp
                                
                                if ($deepVal -eq 'Not Null') {
                                    if ($names.Count -gt 0) { $simulationFound = $true; break }
                                } elseif ($deepProp -like "*MinCount" -and $deepVal -match '^\d+$') {
                                    # Handle numeric MinCount comparison (>= expected value)
                                    $targetMinCount = [int]$deepVal
                                    $highestMinCount = if ($names.Count -gt 0) { ($names | Measure-Object -Maximum).Maximum } else { 0 }
                                    if ($highestMinCount -ge $targetMinCount) { $simulationFound = $true; break }
                                } elseif ($deepVal -match "," -and $deepProp -like "*Name") {
                                    # Handle comma-separated list of required names
                                    $requiredNames = $deepVal -split ',' | ForEach-Object { $_.Trim() }
                                    $allFound = $true
                                    foreach ($reqName in $requiredNames) {
                                        if ($names -notcontains $reqName) {
                                            $allFound = $false
                                            break
                                        }
                                    }
                                    if ($allFound) { $simulationFound = $true; break }
                                } else {
                                    # Standard string matching
                                    if ($names -contains $deepVal) { $simulationFound = $true; break }
                                }
                            }
                        } catch {}
                    }
                }
                
                if ($simulationFound) {
                    $Result.Comments = "No enabled DLP rule found with $deepProp in AdvancedRule. Found matching rule(s) in simulation/test mode"
                } else {
                    $Result.Comments = "No DLP rule found with $deepProp in AdvancedRule"
                }
            }
            return $Result
        } else {
            $Result.Comments = "Invalid AdvancedRule property path. Expected format: AdvancedRule >> PropertyName"
            return $Result
        }
    }
    
    switch ($propertyName) {
        "Workload" {
            if ($script:CurrentDlpControlRules.ContainsKey($controlId)) {
                # Check if the previously matched rule has this workload
                $previousRule = $script:CurrentDlpControlRules[$controlId]
                if ($previousRule.PSObject.Properties.Name -contains "Workload") {
                    # Handle workload as either array or comma-separated string
                    $workloads = @()
                    if ($previousRule.Workload -is [array]) {
                        $workloads = $previousRule.Workload
                    } else {
                        $workloads = $previousRule.Workload -split ',' | ForEach-Object { $_.Trim() }
                    }
                    
                    if ($workloads -contains $ExpectedValue) {
                        $Result.Pass = $true
                        $Result.Comments = "Previously matched DLP rule has workload: $ExpectedValue"
                        return $Result
                    }
                }
            }

            # Look for a rule in enabled policies that has the required workload
            foreach ($rule in $enabledRules) {
                if ($rule.PSObject.Properties.Name -contains "Workload") {
                    # Handle workload as either array or comma-separated string
                    $workloads = @()
                    if ($rule.Workload -is [array]) {
                        $workloads = $rule.Workload
                    } else {
                        $workloads = $rule.Workload -split ',' | ForEach-Object { $_.Trim() }
                    }
                    
                    if ($workloads -contains $ExpectedValue) {
                        # Store this rule for the control ID
                        $script:CurrentDlpControlRules[$controlId] = $rule
                        $Result.Pass = $true
                        $Result.Comments = "Found DLP rule with workload: $ExpectedValue in enabled policy"
                        return $Result
                    }
                }
            }
            
            # Check simulation rules for informational comments
            $simulationMatches = @()
            foreach ($rule in $simulationRules) {
                if ($rule.PSObject.Properties.Name -contains "Workload") {
                    # Handle workload as either array or comma-separated string
                    $workloads = @()
                    if ($rule.Workload -is [array]) {
                        $workloads = $rule.Workload
                    } else {
                        $workloads = $rule.Workload -split ',' | ForEach-Object { $_.Trim() }
                    }
                    
                    if ($workloads -contains $ExpectedValue) {
                        $simulationMatches += $rule
                    }
                }
            }
            
            if ($simulationMatches.Count -gt 0) {
                $Result.Comments = "No enabled DLP rule found with workload: $ExpectedValue. Found $($simulationMatches.Count) rule(s) in simulation/test mode"
            } else {
                $Result.Comments = "No DLP rule found with workload: $ExpectedValue"
            }
        }
        "PrependSubject" {
            if ($script:CurrentDlpControlRules.ContainsKey($controlId)) {
                # Check if the previously matched rule has PrependSubject
                $previousRule = $script:CurrentDlpControlRules[$controlId]
                if ($previousRule.PSObject.Properties.Name -contains "PrependSubject" -and $previousRule.PrependSubject) {
                    $Result.Pass = $true
                    $Result.Comments = "Previously matched DLP rule has PrependSubject configured"
                    return $Result
                }
            }

            # Look for a rule in enabled policies that has PrependSubject configured
            foreach ($rule in $enabledRules) {
                if ($rule.PSObject.Properties.Name -contains "PrependSubject" -and $rule.PrependSubject) {
                    # Store this rule for the control ID
                    $script:CurrentDlpControlRules[$controlId] = $rule
                    $Result.Pass = $true
                    $Result.Comments = "Found DLP rule with PrependSubject configured in enabled policy"
                    return $Result
                }
            }
            
            # Check simulation rules for informational comments
            $simulationMatches = $simulationRules | Where-Object { 
                $_.PSObject.Properties.Name -contains "PrependSubject" -and $_.PrependSubject 
            }
            
            if ($simulationMatches.Count -gt 0) {
                $Result.Comments = "No enabled DLP rule found with PrependSubject configured. Found $($simulationMatches.Count) rule(s) in simulation/test mode"
            } else {
                $Result.Comments = "No DLP rule found with PrependSubject configured"
            }
        }
        "SetHeader" {
            if ($script:CurrentDlpControlRules.ContainsKey($controlId)) {
                # Check if the previously matched rule has SetHeader
                $previousRule = $script:CurrentDlpControlRules[$controlId]
                if ($previousRule.PSObject.Properties.Name -contains "SetHeader" -and $previousRule.SetHeader) {
                    $Result.Pass = $true
                    $Result.Comments = "Previously matched DLP rule has SetHeader configured"
                    return $Result
                }
            }

            # Look for a rule in enabled policies that has SetHeader configured
            foreach ($rule in $enabledRules) {
                if ($rule.PSObject.Properties.Name -contains "SetHeader" -and $rule.SetHeader) {
                    # Store this rule for the control ID
                    $script:CurrentDlpControlRules[$controlId] = $rule
                    $Result.Pass = $true
                    $Result.Comments = "Found DLP rule with SetHeader configured in enabled policy"
                    return $Result
                }
            }
            
            # Check simulation rules for informational comments
            $simulationMatches = $simulationRules | Where-Object { 
                $_.PSObject.Properties.Name -contains "SetHeader" -and $_.SetHeader 
            }
            
            if ($simulationMatches.Count -gt 0) {
                $Result.Comments = "No enabled DLP rule found with SetHeader configured. Found $($simulationMatches.Count) rule(s) in simulation/test mode"
            } else {
                $Result.Comments = "No DLP rule found with SetHeader configured"
            }
        }
        default {
            $Result.Comments = "Unknown DLP rule property: $propertyName"
        }
    }
    return $Result
}

# Helper: Recursively search for a property (e.g., Sensitivetypes > Name) in AdvancedRule JSON
function Get-DlpDeepProperty {
    param([object]$Node, [string]$Target)
    $results = @()
    if ($null -eq $Node) { return $results }
    
    # Handle compound property paths like "Sensitivetypes > Name"
    if ($Target -like "*>*") {
        $parts = $Target -split '\s*>\s*'
        $containerProp = $parts[0]
        $targetProp = $parts[1]
        
        # First find the container (e.g., "Sensitivetypes")
        $containers = Get-DlpDeepProperty -Node $Node -Target $containerProp
        
        # Then extract the target property from each container item
        foreach ($container in $containers) {
            if ($container) {
                # Try case-insensitive property matching
                $matchingProp = $container.PSObject.Properties | Where-Object { $_.Name -ieq $targetProp }
                if ($matchingProp) {
                    $value = $matchingProp.Value
                    if ($value) { $results += $value }
                }
            }
        }
        return $results
    }
    
    # Original logic for simple properties with case-insensitive matching
    if ($Node -is [System.Collections.IEnumerable] -and -not ($Node -is [string])) {
        foreach ($item in $Node) {
            $results += Get-DlpDeepProperty -Node $item -Target $Target
        }
    } elseif ($Node -is [hashtable] -or $Node -is [PSCustomObject]) {
        foreach ($key in $Node.PSObject.Properties.Name) {
            $propValue = $Node.PSObject.Properties[$key].Value
            # Use case-insensitive comparison for property names
            if ($key -ieq $Target -and $propValue) {
                if ($propValue -is [System.Collections.IEnumerable] -and -not ($propValue -is [string])) {
                    foreach ($item in $propValue) {
                        $results += $item
                    }
                } else {
                    $results += $propValue
                }
            } elseif ($propValue -ne $null) {
                $results += Get-DlpDeepProperty -Node $propValue -Target $Target
            }
        }
    }
    return $results
}
function Test-ControlBook {
    param(
        [Parameter(Mandatory = $true)]
        [string]$ControlConfigPath,
        
        [Parameter(Mandatory = $true)]
        [string]$PropertyConfigPath,
        
        [Parameter(Mandatory = $true)]
        [string]$OptimizedReportPath,
        
        [Parameter(Mandatory = $true)]
        [string]$OutputPath,
        
        [Parameter(Mandatory = $false)]
        [string]$ConfigurationName = "Control Book Assessment"
    )
    
    Write-Host "Starting $ConfigurationName Control Evaluation..."
    
    # Load configuration files
    $controls = Import-Csv -Path $ControlConfigPath
    $properties = Import-Csv -Path $PropertyConfigPath
    
    # Load optimized report
    Write-Host "Loading OptimizedReport.json..."
    $optimizedReport = Get-Content -Path $OptimizedReportPath -Raw | ConvertFrom-Json
    
    # Extract published labels for reference
    $publishedLabels = Get-PublishedLabels -OptimizedReport $optimizedReport
    $allLabels = $optimizedReport.GetLabel
    Write-Host "Found $($publishedLabels.Count) published labels out of $($allLabels.Count) total labels"
    
    # Initialize results array
    $results = @()
    
    # Process each control
    foreach ($control in $controls) {
        $controlProperties = $properties | Where-Object { $_.ControlID -eq $control.ControlID }
        $controlMaturity = $null
        if ($control.PSObject.Properties["MaturityLevel"]) {
            $controlMaturity = $control.MaturityLevel
        } elseif ($control.PSObject.Properties["Maturity Level"]) {
            $controlMaturity = $control."Maturity Level"
        }
        foreach ($property in $controlProperties) {
            Write-Host "Evaluating $($control.ControlID) - $($property.Properties)"
            $propertyMaturity = $null
            if ($property.PSObject.Properties["MaturityLevel"]) { $propertyMaturity = $property.MaturityLevel }
            $result = Test-ControlProperty -Control $control -Property $property -OptimizedReport $optimizedReport -PublishedLabels $publishedLabels -AllLabels $allLabels -ConfigurationName $ConfigurationName
            # Always set MaturityLevel in result, prefer property, then control
            if ($propertyMaturity) {
                $result.MaturityLevel = $propertyMaturity
            } elseif ($controlMaturity) {
                $result.MaturityLevel = $controlMaturity
            } else {
                $result.MaturityLevel = ''
            }
            $results += $result
        }
    }
    
    # Export results to CSV
    $results | Export-Csv -Path $OutputPath -NoTypeInformation
    Write-Host "$ConfigurationName control evaluation complete. Results saved to: $OutputPath"
    
    # Return summary statistics
    $passCount = ($results | Where-Object { $_.Pass -eq $true }).Count
    $totalCount = $results.Count
    $passPercentage = if ($totalCount -gt 0) { [math]::Round(($passCount / $totalCount) * 100, 1) } else { 0 }
    
    Write-Host ""
    Write-Host "=== $ConfigurationName Summary ===" -ForegroundColor Cyan
    Write-Host "Total Controls Evaluated: $totalCount" -ForegroundColor White
    Write-Host "Controls Passing: $passCount" -ForegroundColor Green
    Write-Host "Controls Failing: $($totalCount - $passCount)" -ForegroundColor Red
    Write-Host "Compliance Rate: $passPercentage%" -ForegroundColor $(if ($passPercentage -ge 80) { "Green" } elseif ($passPercentage -ge 60) { "Yellow" } else { "Red" })
    
    return @{
        TotalControls = $totalCount
        PassingControls = $passCount
        FailingControls = ($totalCount - $passCount)
        ComplianceRate = $passPercentage
        Results = $results
    }
}

function Get-PublishedLabels {
    param($OptimizedReport)
    
    $publishedLabels = @()
    
    # Get all labels that are marked as Published = true
    $allLabels = $optimizedReport.GetLabel | Where-Object { $_.Published -eq $true }
    
    # Also cross-reference with LabelPolicy to ensure they're actually scoped
    $labelPolicies = $optimizedReport.GetLabelPolicy
    $scopedLabelIds = @()
    
    foreach ($policy in $labelPolicies) {
        if ($policy.ScopedLabels) {
            $scopedLabelIds += $policy.ScopedLabels
        }
    }
    
    # Filter to only include labels that are both Published=true and in policy scope
    foreach ($label in $allLabels) {
        if ($label.ImmutableId -in $scopedLabelIds) {
            $publishedLabels += $label
        }
    }
    
    return $publishedLabels
}

function Test-ControlProperty {
    param($Control, $Property, $OptimizedReport, $PublishedLabels, $AllLabels, $ConfigurationName)
    
    $maturityLevel = $null
    if ($Control.PSObject.Properties["MaturityLevel"]) {
        $maturityLevel = $Control.MaturityLevel
    } elseif ($Control.PSObject.Properties["Maturity Level"]) {
        $maturityLevel = $Control."Maturity Level"
    } elseif ($Property.PSObject.Properties["MaturityLevel"]) {
        $maturityLevel = $Property.MaturityLevel
    } elseif ($Property.PSObject.Properties["Maturity Level"]) {
        $maturityLevel = $Property."Maturity Level"
    }
    $result = [PSCustomObject]@{
        Capability = $Control.Capability
        ControlID = $Control.ControlID
        Control = $Control.Control
        Properties = $Property.Properties
        DefaultValue = $Property.DefaultValue
        MustConfigure = $Property.MustConfigure
        MaturityLevel = $maturityLevel
        Pass = $false
        Comments = ""
        ConfigurationName = $ConfigurationName
    }
    
    try {
        # Parse the property path (e.g., "GetLabel > Published")
        $propertyParts = $Property.Properties -split ' > '
        $dataSource = $propertyParts[0].Trim()
        
        # Get expected value
        $expectedValue = if ($Property.MustConfigure -eq $true -and $Property.DefaultValue) {
            $Property.DefaultValue
        } else {
            $Property.DefaultValue
        }
        
        switch ($dataSource) {
            "GetLabel" {
                $result = Test-GetLabelProperty -Result $result -PropertyParts $propertyParts -ExpectedValue $expectedValue -PublishedLabels $PublishedLabels -AllLabels $AllLabels
            }
            "GetLabelPolicy" {
                $result = Test-GetLabelPolicyProperty -Result $result -PropertyParts $propertyParts -ExpectedValue $expectedValue -OptimizedReport $OptimizedReport -AllLabels $AllLabels
            }
            "GetAutoSensitivityLabelPolicy" {
                $result = Test-GetAutoSensitivityLabelPolicyProperty -Result $result -PropertyParts $propertyParts -ExpectedValue $expectedValue -OptimizedReport $OptimizedReport -AllLabels $AllLabels
            }
            "GetDlpComplianceRule" {
                $result = Test-GetDlpComplianceRuleProperty -Result $result -PropertyParts $propertyParts -ExpectedValue $expectedValue -OptimizedReport $OptimizedReport
            }
            "GetDlpCompliancePolicy" {
                $result = Test-GetDlpCompliancePolicyProperty -Result $result -PropertyParts $propertyParts -ExpectedValue $expectedValue -OptimizedReport $OptimizedReport
            }
            default {
                $result.Comments = "Unknown data source: $dataSource"
            }
        }
    }
    catch {
        $result.Comments = "Error evaluating property: $($_.Exception.Message)"
    }
    
    return $result
}

function Test-GetLabelProperty {
    param($Result, $PropertyParts, $ExpectedValue, $PublishedLabels, $AllLabels)
    
    if ($PropertyParts.Count -lt 2) {
        $Result.Comments = "Invalid property path"
        return $Result
    }
    
    $propertyName = $PropertyParts[1].Trim()
    
    # Handle >> operator for deep property access
    $deepProperty = $null
    if ($propertyName -like "*>>*") {
        $deepParts = $propertyName -split '\s*>>\s*'
        $propertyName = $deepParts[0].Trim()
        $deepProperty = $deepParts[1].Trim()
    }
    
    switch ($propertyName) {
        "Published" {
            # Check if any labels are published
            if ($PublishedLabels.Count -gt 0) {
                $Result.Pass = $true
                $Result.Comments = "Found $($PublishedLabels.Count) published labels"
            } else {
                $Result.Comments = "No published labels found"
            }
        }
        "DisplayName" {
            # Check for comma-separated required labels
            $requiredLabels = $ExpectedValue -split ',' | ForEach-Object { $_.Trim() }
            $foundPublishedLabels = @()
            $foundUnpublishedLabels = @()
            $missingLabels = @()
            
            foreach ($requiredLabel in $requiredLabels) {
                $publishedMatch = $PublishedLabels | Where-Object { $_.DisplayName -ieq $requiredLabel }
                $allMatch = $AllLabels | Where-Object { $_.DisplayName -ieq $requiredLabel }
                
                if ($publishedMatch) {
                    $foundPublishedLabels += $requiredLabel
                } elseif ($allMatch) {
                    $foundUnpublishedLabels += $requiredLabel
                } else {
                    $missingLabels += $requiredLabel
                }
            }
            
            if ($foundPublishedLabels.Count -eq $requiredLabels.Count) {
                $Result.Pass = $true
                $Result.Comments = "All required labels found and published: $($foundPublishedLabels -join ', ')"
            } else {
                $comments = @()
                if ($foundPublishedLabels.Count -gt 0) {
                    $comments += "Published: $($foundPublishedLabels -join ', ')"
                }
                if ($foundUnpublishedLabels.Count -gt 0) {
                    $comments += "Exists but not published: $($foundUnpublishedLabels -join ', ')"
                }
                if ($missingLabels.Count -gt 0) {
                    $comments += "Not found: $($missingLabels -join ', ')"
                }
                $Result.Comments = $comments -join '. '
            }
        }
        "LabelActions" {
            if ($PropertyParts.Count -lt 3) {
                $Result.Comments = "Invalid LabelActions property path"
                return $Result
            }
            
            $actionProperty = $PropertyParts[2].Trim()
            if ($actionProperty -eq "Type") {
                $labelsWithAction = @()
                foreach ($label in $PublishedLabels) {
                    $labelActions = Parse-LabelActions -Label $label
                    $hasAction = $labelActions | Where-Object { $_.Type -eq $ExpectedValue }
                    if ($hasAction) {
                        $labelsWithAction += $label.DisplayName
                    }
                }
                
                if ($labelsWithAction.Count -gt 0) {
                    $Result.Pass = $true
                    $Result.Comments = "Labels with ${ExpectedValue} action: $($labelsWithAction -join ', ')"
                } else {
                    $Result.Comments = "No published labels found with ${ExpectedValue} action"
                }
            }
        }
        "ContentType" {
            $expectedTypes = $ExpectedValue -split ',' | ForEach-Object { $_.Trim() }
            $labelsWithContentType = @()
            
            foreach ($label in $PublishedLabels) {
                $contentTypes = Parse-ContentType -Label $label
                $hasAllTypes = $true
                foreach ($expectedType in $expectedTypes) {
                    if ($expectedType -notin $contentTypes) {
                        $hasAllTypes = $false
                        break
                    }
                }
                if ($hasAllTypes) {
                    $labelsWithContentType += $label.DisplayName
                }
            }
            
            if ($labelsWithContentType.Count -gt 0) {
                $Result.Pass = $true
                $Result.Comments = "Labels with required content types: $($labelsWithContentType -join ', ')"
            } else {
                $Result.Comments = "No published labels found with required content types: $($expectedTypes -join ', ')"
            }
        }
        "Conditions" {
            # Handle both old format (GetLabel > Conditions > Key) and new format (GetLabel > Conditions >> Key)
            $conditionProperty = $null
            if ($deepProperty) {
                # New format with >> operator
                $conditionProperty = $deepProperty
            } elseif ($PropertyParts.Count -ge 3) {
                # Old format with single > operator
                $conditionProperty = $PropertyParts[2].Trim()
            } else {
                $Result.Comments = "Invalid Conditions property path"
                return $Result
            }
            
            # For Sensitivity Auto-labelling controls, only consider labels from the taxonomy defined in DisplayName control
            $labelsToCheck = $PublishedLabels
            if ($Result.Capability -eq "Sensitivity Auto-labelling") {
                # Get the required labels from the DisplayName control in the same configuration
                $taxonomyControl = Get-TaxonomyLabels -AllLabels $AllLabels -Result $Result
                $taxonomyLabelNames = $taxonomyControl.RequiredLabels
                $taxonomyLabels = $PublishedLabels | Where-Object { $_.DisplayName -in $taxonomyLabelNames }
                $outOfScopeLabels = @()
                
                # Check all published labels for the condition, but separate taxonomy from others
                foreach ($label in $PublishedLabels) {
                    $conditions = Parse-LabelConditions -Label $label
                    $hasCondition = $false
                    
                    if ($conditionProperty -eq "Key" -and $conditions.ContainsKey($ExpectedValue)) {
                        $hasCondition = $true
                    } elseif ($conditionProperty -eq "Value" -and $conditions.ContainsValue($ExpectedValue)) {
                        $hasCondition = $true
                    }
                    
                    if ($hasCondition -and $label.DisplayName -notin $taxonomyLabelNames) {
                        $outOfScopeLabels += $label.DisplayName
                    }
                }
                
                $labelsToCheck = $taxonomyLabels
                $labelsWithCondition = @()
                foreach ($label in $taxonomyLabels) {
                    $conditions = Parse-LabelConditions -Label $label
                    if ($conditionProperty -eq "Key" -and $conditions.ContainsKey($ExpectedValue)) {
                        $labelsWithCondition += $label.DisplayName
                    } elseif ($conditionProperty -eq "Value" -and $conditions.ContainsValue($ExpectedValue)) {
                        $labelsWithCondition += $label.DisplayName
                    }
                }
                
                if ($labelsWithCondition.Count -gt 0) {
                    $Result.Pass = $true
                    $comments = "Taxonomy labels with condition ${conditionProperty} = ${ExpectedValue}: $($labelsWithCondition -join ', ')"
                    if ($outOfScopeLabels.Count -gt 0) {
                        $comments += ". Out-of-scope labels also have this condition: $($outOfScopeLabels -join ', ')"
                    }
                    $Result.Comments = $comments
                } else {
                    $comments = "No taxonomy labels found with condition ${conditionProperty} = ${ExpectedValue}"
                    if ($outOfScopeLabels.Count -gt 0) {
                        $comments += ". Out-of-scope labels have this condition: $($outOfScopeLabels -join ', ')"
                    }
                    $Result.Comments = $comments
                }
            } else {
                # For non-auto-labeling controls, check all published labels
                $labelsWithCondition = @()
                foreach ($label in $PublishedLabels) {
                    $conditions = Parse-LabelConditions -Label $label
                    if ($conditionProperty -eq "Key" -and $conditions.ContainsKey($ExpectedValue)) {
                        $labelsWithCondition += $label.DisplayName
                    } elseif ($conditionProperty -eq "Value" -and $conditions.ContainsValue($ExpectedValue)) {
                        $labelsWithCondition += $label.DisplayName
                    }
                }
                
                if ($labelsWithCondition.Count -gt 0) {
                    $Result.Pass = $true
                    $Result.Comments = "Labels with condition ${conditionProperty} = ${ExpectedValue}: $($labelsWithCondition -join ', ')"
                } else {
                    $Result.Comments = "No published labels found with condition ${conditionProperty} = ${ExpectedValue}"
                }
            }
        }
        default {
            $Result.Comments = "Unknown GetLabel property: $propertyName"
        }
    }
    
    return $Result
}

function Test-GetLabelPolicyProperty {
    param($Result, $PropertyParts, $ExpectedValue, $OptimizedReport, $AllLabels)
    
    if ($PropertyParts.Count -lt 3) {
        $Result.Comments = "Invalid GetLabelPolicy property path"
        return $Result
    }
    
    $settingName = $PropertyParts[2].Trim()
    $labelPolicies = $OptimizedReport.GetLabelPolicy
    
    # Get the required labels from the taxonomy (dynamically determined from configuration)
    $taxonomyControl = Get-TaxonomyLabels -AllLabels $AllLabels -Result $Result
    $requiredLabelNames = $taxonomyControl.RequiredLabels
    $requiredLabelIds = @()
    
    # Find the ImmutableIds for our required labels from GetLabel
    foreach ($requiredLabel in $requiredLabelNames) {
        $matchingLabel = $AllLabels | Where-Object { $_.DisplayName -eq $requiredLabel }
        if ($matchingLabel) {
            $requiredLabelIds += $matchingLabel.ImmutableId
        }
    }
    
    if ($requiredLabelIds.Count -eq 0) {
        $Result.Comments = "None of the required taxonomy labels ($($requiredLabelNames -join ', ')) found in tenant"
        return $Result
    }
    
    # Find policies that contain any of our required labels
    $relevantPolicies = @()
    foreach ($policy in $labelPolicies) {
        if ($policy.ScopedLabels) {
            $hasRequiredLabel = $false
            foreach ($labelId in $requiredLabelIds) {
                # Check if the ImmutableId is in the policy's ScopedLabels array
                if ($labelId -in $policy.ScopedLabels) {
                    $hasRequiredLabel = $true
                    break
                }
            }
            if ($hasRequiredLabel) {
                $relevantPolicies += $policy
            }
        }
    }
    
    if ($relevantPolicies.Count -eq 0) {
        $Result.Comments = "No label policies found containing the required taxonomy labels"
        return $Result
    }
    
    # Check if ANY of the relevant policies has the required setting
    $policiesWithSetting = @()
    foreach ($policy in $relevantPolicies) {
        $settings = Parse-PolicySettings -Policy $policy
        if ($settings.ContainsKey($settingName)) {
            $actualValue = $settings[$settingName]
            
            # Handle different validation types based on expected value
            $isMatch = $false
            if ($ExpectedValue -eq "Not Null") {
                # For settings that should have any non-null value
                if ($actualValue -and $actualValue -ne "None" -and $actualValue -ne "") {
                    $isMatch = $true
                }
            } else {
                # For exact value matching
                if ($actualValue -eq $ExpectedValue) {
                    $isMatch = $true
                }
            }
            
            $policiesWithSetting += @{
                Policy = $policy
                Value = $actualValue
                Match = $isMatch
            }
        }
    }
    
    $matchingPolicies = $policiesWithSetting | Where-Object { $_.Match -eq $true }
    if ($matchingPolicies.Count -gt 0) {
        $Result.Pass = $true
        if ($ExpectedValue -eq "Not Null") {
            $Result.Comments = "Found $($matchingPolicies.Count) policies containing taxonomy labels with ${settingName} configured (not null)"
        } else {
            $Result.Comments = "Found $($matchingPolicies.Count) policies containing taxonomy labels with ${settingName} = ${ExpectedValue}"
        }
    } else {
        $allValues = ($policiesWithSetting | ForEach-Object { $_.Value }) -join ', '
        if ($ExpectedValue -eq "Not Null") {
            $Result.Comments = "No policies containing taxonomy labels found with ${settingName} configured. Found values: $allValues"
        } else {
            $Result.Comments = "No policies containing taxonomy labels found with ${settingName} = ${ExpectedValue}. Found values: $allValues"
        }
    }
    
    return $Result
}

function Test-GetAutoSensitivityLabelPolicyProperty {
    param($Result, $PropertyParts, $ExpectedValue, $OptimizedReport, $AllLabels)
    
    if ($PropertyParts.Count -lt 2) {
        $Result.Comments = "Invalid GetAutoSensitivityLabelPolicy property path"
        return $Result
    }
    
    $propertyName = $PropertyParts[1].Trim()
    $autoPolicies = $OptimizedReport.GetAutoSensitivityLabelPolicy
    
    # For Sensitivity Auto-labelling controls, filter to only taxonomy-related policies
    $taxonomyControl = Get-TaxonomyLabels -AllLabels $AllLabels -Result $Result
    $taxonomyLabelNames = $taxonomyControl.RequiredLabels
    $taxonomyRelatedPolicies = @()
    $outOfScopePolicies = @()
    
    foreach ($policy in $autoPolicies) {
        $isTaxonomyRelated = $false
        if ($policy.LabelDisplayName) {
            foreach ($taxonomyLabel in $taxonomyLabelNames) {
                if ($policy.LabelDisplayName -like "*$taxonomyLabel*") {
                    $isTaxonomyRelated = $true
                    break
                }
            }
        }
        
        if ($isTaxonomyRelated) {
            $taxonomyRelatedPolicies += $policy
        } else {
            $outOfScopePolicies += $policy
        }
    }
    
    switch ($propertyName) {
        "Mode" {
            $enabledPolicies = $taxonomyRelatedPolicies | Where-Object { $_.Mode -eq $ExpectedValue }
            $outOfScopeEnabledPolicies = $outOfScopePolicies | Where-Object { $_.Mode -eq $ExpectedValue }
            
            if ($enabledPolicies.Count -gt 0) {
                $Result.Pass = $true
                $comments = "Found $($enabledPolicies.Count) taxonomy auto-labeling policies with Mode = ${ExpectedValue}"
                if ($outOfScopeEnabledPolicies.Count -gt 0) {
                    $comments += ". Out-of-scope policies also enabled: $($outOfScopeEnabledPolicies.Count)"
                }
                $Result.Comments = $comments
            } else {
                $comments = "No taxonomy auto-labeling policies found with Mode = ${ExpectedValue}"
                if ($outOfScopeEnabledPolicies.Count -gt 0) {
                    $comments += ". Out-of-scope policies with this mode: $($outOfScopeEnabledPolicies.Count)"
                }
                $Result.Comments = $comments
            }
        }
        "Type" {
            $typePolicies = $taxonomyRelatedPolicies | Where-Object { $_.Type -eq $ExpectedValue }
            $outOfScopeTypePolicies = $outOfScopePolicies | Where-Object { $_.Type -eq $ExpectedValue }
            
            if ($typePolicies.Count -gt 0) {
                $Result.Pass = $true
                $comments = "Found $($typePolicies.Count) taxonomy policies with Type = ${ExpectedValue}"
                if ($outOfScopeTypePolicies.Count -gt 0) {
                    $comments += ". Out-of-scope policies also have this type: $($outOfScopeTypePolicies.Count)"
                }
                $Result.Comments = $comments
            } else {
                $comments = "No taxonomy policies found with Type = ${ExpectedValue}"
                if ($outOfScopeTypePolicies.Count -gt 0) {
                    $comments += ". Out-of-scope policies with this type: $($outOfScopeTypePolicies.Count)"
                }
                $Result.Comments = $comments
            }
        }
        "Workload" {
            $expectedWorkloads = $ExpectedValue -split ',' | ForEach-Object { $_.Trim() }
            $matchingPolicies = @()
            $outOfScopeMatchingPolicies = @()
            
            foreach ($policy in $taxonomyRelatedPolicies) {
                $hasAllWorkloads = $true
                foreach ($expectedWorkload in $expectedWorkloads) {
                    $workloadFound = $false
                    # Check various workload properties
                    if ($expectedWorkload -eq "SharePoint" -and $policy.SharePointLocation) {
                        $workloadFound = $true
                    } elseif ($expectedWorkload -eq "OneDriveForBusiness" -and $policy.OneDriveLocation) {
                        $workloadFound = $true
                    } elseif ($expectedWorkload -eq "Exchange" -and $policy.ExchangeLocation) {
                        $workloadFound = $true
                    }
                    
                    if (-not $workloadFound) {
                        $hasAllWorkloads = $false
                        break
                    }
                }
                
                if ($hasAllWorkloads) {
                    $matchingPolicies += $policy
                }
            }
            
            # Check out-of-scope policies too for reporting
            foreach ($policy in $outOfScopePolicies) {
                $hasAllWorkloads = $true
                foreach ($expectedWorkload in $expectedWorkloads) {
                    $workloadFound = $false
                    if ($expectedWorkload -eq "SharePoint" -and $policy.SharePointLocation) {
                        $workloadFound = $true
                    } elseif ($expectedWorkload -eq "OneDriveForBusiness" -and $policy.OneDriveLocation) {
                        $workloadFound = $true
                    } elseif ($expectedWorkload -eq "Exchange" -and $policy.ExchangeLocation) {
                        $workloadFound = $true
                    }
                    
                    if (-not $workloadFound) {
                        $hasAllWorkloads = $false
                        break
                    }
                }
                
                if ($hasAllWorkloads) {
                    $outOfScopeMatchingPolicies += $policy
                }
            }
            
            if ($matchingPolicies.Count -gt 0) {
                $Result.Pass = $true
                $comments = "Found $($matchingPolicies.Count) taxonomy policies with required workloads: $($expectedWorkloads -join ', ')"
                if ($outOfScopeMatchingPolicies.Count -gt 0) {
                    $comments += ". Out-of-scope policies also have these workloads: $($outOfScopeMatchingPolicies.Count)"
                }
                $Result.Comments = $comments
            } else {
                $comments = "No taxonomy policies found with required workloads: $($expectedWorkloads -join ', ')"
                if ($outOfScopeMatchingPolicies.Count -gt 0) {
                    $comments += ". Out-of-scope policies have these workloads: $($outOfScopeMatchingPolicies.Count)"
                }
                $Result.Comments = $comments
            }
        }
        default {
            $Result.Comments = "Unknown GetAutoSensitivityLabelPolicy property: $propertyName"
        }
    }
    
    return $Result
}

function Get-TaxonomyLabels {
    param($AllLabels, $Result)
    
    # This function dynamically determines the required taxonomy labels from the configuration
    # It looks for a DisplayName control in the same capability that defines the taxonomy
    
    # For now, we'll use the PSPF taxonomy as default, but this could be made more dynamic
    # by reading from the configuration files to find taxonomy-defining controls
    $defaultTaxonomy = @("UNOFFICIAL", "OFFICIAL", "OFFICIAL SENSITIVE")
    
    return @{
        RequiredLabels = $defaultTaxonomy
        Source = "Default PSPF Taxonomy"
    }
}

# Helper functions for parsing complex data structures
function Parse-LabelActions {
    param($Label)
    
    $actions = @()
    if ($Label.LabelActions) {
        foreach ($actionJson in $Label.LabelActions) {
            try {
                $action = $actionJson | ConvertFrom-Json
                $actions += $action
            } catch {
                # Handle malformed JSON
            }
        }
    }
    return $actions
}

function Parse-ContentType {
    param($Label)
    
    $contentTypes = @()
    if ($Label.Settings) {
        foreach ($setting in $Label.Settings) {
            if ($setting -match '\[contenttype,\s*(.+?)\]') {
                $types = $matches[1] -split ',' | ForEach-Object { $_.Trim() }
                $contentTypes += $types
            }
        }
    }
    return $contentTypes
}

function Parse-LabelConditions {
    param($Label)
    
    $conditions = @{}
    if ($Label.Conditions) {
        try {
            $conditionsObj = $Label.Conditions | ConvertFrom-Json
            # Parse the complex conditions structure recursively
            Parse-ConditionsRecursively -Node $conditionsObj -Conditions ([ref]$conditions)
        } catch {
            # Handle malformed JSON
        }
    }
    return $conditions
}

function Parse-ConditionsRecursively {
    param($Node, [ref]$Conditions)
    
    if ($null -eq $Node) { return }
    
    # Handle arrays
    if ($Node -is [System.Collections.IEnumerable] -and -not ($Node -is [string])) {
        foreach ($item in $Node) {
            Parse-ConditionsRecursively -Node $item -Conditions $Conditions
        }
        return
    }
    
    # Handle objects
    if ($Node -is [hashtable] -or $Node -is [PSCustomObject]) {
        # Check for Settings array (this is where Key/Value pairs are stored)
        if ($Node.Settings) {
            foreach ($setting in $Node.Settings) {
                if ($setting.Key -and $setting.Value) {
                    $Conditions.Value[$setting.Key] = $setting.Value
                }
            }
        }
        
        # Recursively parse all properties (And, Or, etc.)
        foreach ($prop in $Node.PSObject.Properties) {
            if ($prop.Value) {
                Parse-ConditionsRecursively -Node $prop.Value -Conditions $Conditions
            }
        }
    }
}

function Parse-PolicySettings {
    param($Policy)
    
    $settings = @{}
    if ($Policy.Settings) {
        foreach ($setting in $Policy.Settings) {
            if ($setting -match '\[(.+?),\s*(.+?)\]') {
                $key = $matches[1].Trim()
                $value = $matches[2].Trim()
                $settings[$key] = $value
            }
        }
    }
    return $settings
}