Private/Scanning/Add-PolicyToAssignmentIndex.ps1

function Add-PolicyToAssignmentIndex {
    <#
    .SYNOPSIS
    Indexes a policy assignment for fast baseline matching lookups.
     
    .DESCRIPTION
    Expands a policy assignment and adds all member policies to three index structures:
    - ById: Fast lookup by full policy definition ID
    - ByNormName: Fast lookup by normalized policy name
    - Effects: Stores resolved effect values per policy
     
    This function builds the indexes used to determine which baseline policies are assigned
    in the tenant. It handles both policy sets and individual policies, with exclusion support.
     
    .PARAMETER Assignment
    The policy assignment object to index. Must have PolicyDefinitionId property.
     
    .PARAMETER ByIdIndex
    Hashtable for ID-based lookups. Keys are policy definition IDs, values are $true.
     
    .PARAMETER ByNormNameIndex
    Hashtable for name-based lookups. Keys are normalized policy names, values are $true.
     
    .PARAMETER EffectsIndex
    Hashtable storing effect values. Keys are policy IDs or normalized names, values are effect strings.
     
    .PARAMETER PolicyDefinitionCache
    Hashtable cache for policy definitions (prevents redundant API calls).
     
    .PARAMETER PolicySetDefinitionCache
    Hashtable cache for policy set definitions (prevents redundant API calls).
     
    .PARAMETER ExcludeList
    Optional array of assignment names/IDs to exclude from indexing.
     
    .EXAMPLE
    Add-PolicyToAssignmentIndex -Assignment $assignment `
                                -ByIdIndex $assignedById `
                                -ByNormNameIndex $assignedByNormName `
                                -EffectsIndex $assignedEffects `
                                -PolicyDefinitionCache $script:policyDefinitionCache `
                                -PolicySetDefinitionCache $script:policySetDefinitionCache
     
    Indexes a policy assignment for baseline matching.
     
    .OUTPUTS
    None. Modifies the provided index hashtables in place.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object]$Assignment,
        
        [Parameter(Mandatory)]
        [hashtable]$ByIdIndex,
        
        [Parameter(Mandatory)]
        [hashtable]$ByNormNameIndex,
        
        [Parameter(Mandatory)]
        [hashtable]$EffectsIndex,
        
        [Parameter(Mandatory)]
        [hashtable]$PolicyDefinitionCache,
        
        [Parameter(Mandatory)]
        [hashtable]$PolicySetDefinitionCache,
        
        [Parameter()]
        [string[]]$ExcludeList = @()
    )
    
    $isIndividualPolicy = $Assignment.PolicyDefinitionId -like "*/policyDefinitions/*" -and 
                          $Assignment.PolicyDefinitionId -notlike "*/policySetDefinitions/*"
    
    if ($isIndividualPolicy) {
        # Handle individual policy assignment
        try {
            $policy = Get-PolicyDefinitionCached -PolicyDefinitionId $Assignment.PolicyDefinitionId `
                                                  -Cache $PolicyDefinitionCache
            
            Write-Debug "Individual policy detected for index: $($policy.DisplayName)"
            
            # Add to ID index
            $ByIdIndex[$policy.Id] = $true
            
            # Add to normalized name index
            $normName = Normalize-PolicyName $policy.DisplayName
            if ($normName) {
                $ByNormNameIndex[$normName] = $true
                Write-Debug " └─ Added to index: $normName"
            }
            
            # Resolve and store effect
            try {
                $effectValue = Resolve-PolicyEffect -Ref $null `
                                                    -PolicyDefinition $policy `
                                                    -PolicyDisplayName $policy.DisplayName `
                                                    -PolicyDefinitionId $policy.Id
                $EffectsIndex[$policy.Id] = $effectValue
                if ($normName) {
                    $EffectsIndex[$normName] = $effectValue
                }
            }
            catch {
                Write-Debug " └─ Cannot resolve effect for individual policy"
                $EffectsIndex[$policy.Id] = "N/A"
                if ($normName) {
                    $EffectsIndex[$normName] = "N/A"
                }
            }
        }
        catch {
            Write-Debug "Cannot load individual policy: $($Assignment.PolicyDefinitionId)"
        }
        
        return
    }
    
    # Handle policy set (initiative) assignment
    try {
        $set = Get-PolicySetDefinitionCached -Id $Assignment.PolicyDefinitionId `
                                             -Cache $PolicySetDefinitionCache
    }
    catch {
        Write-Debug "Cannot load policy set: $($Assignment.PolicyDefinitionId)"
        return
    }
    
    # Check exclusion list
    if (Test-AssignmentExcluded -Assignment $Assignment -PolicySet $set -ExcludeList $ExcludeList) {
        Write-Debug "Excluded assignment: $($set.DisplayName)"
        return
    }
    
    # Index all member policies
    foreach ($ref in $set.PolicyDefinition) {
        try {
            $p = Get-PolicyDefinitionCached -PolicyDefinitionId $ref.PolicyDefinitionId `
                                            -Cache $PolicyDefinitionCache
        }
        catch {
            $p = $null
        }
        
        $policyId   = if ($p) { $p.Id } else { $ref.PolicyDefinitionId }
        $policyName = if ($p) { $p.DisplayName } else { "[Unknown] $policyId" }
        
        # Resolve effect value (from ref parameters, policy parameters, or rule)
        $effectValue = $null
        
        if ($ref.parameters -and $ref.parameters.effect -and $ref.parameters.effect.value) {
            $effectValue = $ref.parameters.effect.value
        }
        
        if (-not $effectValue -and $p) {
            if ($p.Parameter.effect -and $p.Parameter.effect.defaultValue) {
                $effectValue = $p.Parameter.effect.defaultValue
            }
        }
        
        if (-not $effectValue -and $p -and $p.PolicyRule -and $p.PolicyRule.then -and $p.PolicyRule.then.effect) {
            $rawEffect = $p.PolicyRule.then.effect
            
            if ($rawEffect -match "\[parameters\('(\w+)'\)\]") {
                $paramName = $Matches[1]
                if ($p.Parameter.$paramName -and $p.Parameter.$paramName.defaultValue) {
                    $effectValue = $p.Parameter.$paramName.defaultValue
                } else {
                    $effectValue = "Parametrized"
                }
            } else {
                $effectValue = $rawEffect
            }
        }
        
        if (-not $effectValue) {
            $effectValue = "N/A"
        }
        
        # Add to ID index
        if ($policyId) { 
            $ByIdIndex[$policyId] = $true 
            $EffectsIndex[$policyId] = $effectValue
        }
        
        # Add to normalized name index
        $nk = Normalize-PolicyName $policyName
        if ($nk) { 
            $ByNormNameIndex[$nk] = $true 
            $EffectsIndex[$nk] = $effectValue
        }
    }
}