Private/Scanning/Expand-PolicySetMembers.ps1

function Expand-PolicySetMembers {
    <#
    .SYNOPSIS
    Expands a policy assignment into individual policy objects with resolved effects.
     
    .DESCRIPTION
    Takes a policy assignment and expands it into an array of individual policy definitions.
    Handles both policy sets (initiatives) and individual policy assignments.
    Resolves effect values from assignment parameters, policy parameters, or rule definitions.
     
    This function is critical for comparing assigned policies against baselines, as it
    normalizes all assignment types into individual policy objects.
     
    .PARAMETER Assignment
    The policy assignment object to expand. Must have PolicyDefinitionId property.
     
    .PARAMETER PolicyDefinitionCache
    Hashtable cache for policy definitions (prevents redundant API calls).
     
    .PARAMETER PolicySetDefinitionCache
    Hashtable cache for policy set definitions (prevents redundant API calls).
     
    .EXAMPLE
    $members = Expand-PolicySetMembers -Assignment $assignment `
                                       -PolicyDefinitionCache $script:policyDefinitionCache `
                                       -PolicySetDefinitionCache $script:policySetDefinitionCache
     
    Expands an assignment into individual policy objects with resolved effects.
     
    .OUTPUTS
    Array of PSCustomObject with properties:
    - PolicyDefinitionId: Full resource ID of the policy
    - PolicyDisplayName: Human-readable name of the policy
    - PolicyType: Type of policy (BuiltIn, Custom, etc.)
    - Version: Policy version from metadata
    - Effect: Resolved effect value (Audit, Deny, etc.)
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object]$Assignment,
        
        [Parameter(Mandatory)]
        [hashtable]$PolicyDefinitionCache,
        
        [Parameter(Mandatory)]
        [hashtable]$PolicySetDefinitionCache
    )
    
    $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 "Processing individual policy: $($policy.DisplayName)"
            
            $effectValue = Resolve-PolicyEffect -Ref $null `
                                                -PolicyDefinition $policy `
                                                -PolicyDisplayName $policy.DisplayName `
                                                -PolicyDefinitionId $policy.Id
            
            return [PSCustomObject]@{
                PolicyDefinitionId = $policy.Id
                PolicyDisplayName  = $policy.DisplayName
                PolicyType         = $policy.PolicyType
                Version            = $policy.Metadata.version
                Effect             = $effectValue
            }
        }
        catch {
            Write-Warning "Failed to expand individual policy: $($Assignment.DisplayName) - $($_.Exception.Message)"
            return [PSCustomObject]@{
                PolicyDefinitionId = $Assignment.PolicyDefinitionId
                PolicyDisplayName  = "[Unresolved] $($Assignment.PolicyDefinitionId)"
                PolicyType         = $null
                Version            = $null
                Effect             = "N/A"
            }
        }
    }
    else {
        # Handle policy set (initiative) assignment
        try {
            $set = Get-PolicySetDefinitionCached -Id $Assignment.PolicyDefinitionId `
                                                 -Cache $PolicySetDefinitionCache
            
            Write-Debug "Expanding policy set: $($set.DisplayName) with $($set.PolicyDefinition.Count) members"
        }
        catch {
            Write-Warning "Cannot load policy set: $($Assignment.DisplayName) - $($_.Exception.Message)"
            return @()
        }
        
        # Expand all policy definitions in the set
        $members = foreach ($ref in $set.PolicyDefinition) {
            try {
                $pol = Get-PolicyDefinitionCached -PolicyDefinitionId $ref.PolicyDefinitionId `
                                                  -Cache $PolicyDefinitionCache
                
                $effectValue = Resolve-PolicyEffect -Ref $ref `
                                                    -PolicyDefinition $pol `
                                                    -PolicyDisplayName $pol.DisplayName `
                                                    -PolicyDefinitionId $pol.Id
                
                [PSCustomObject]@{
                    PolicyDefinitionId = $pol.Id
                    PolicyDisplayName  = $pol.DisplayName
                    PolicyType         = $pol.PolicyType
                    Version            = $pol.Metadata.version
                    Effect             = $effectValue
                }
            }
            catch {
                Write-Warning "Failed to get policy definition: $($ref.PolicyDefinitionId) - $($_.Exception.Message)"
                [PSCustomObject]@{
                    PolicyDefinitionId = $ref.PolicyDefinitionId
                    PolicyDisplayName  = "[Unresolved] $($ref.PolicyDefinitionId)"
                    PolicyType         = $null
                    Version            = $null
                    Effect             = "N/A"
                }
            }
        }
        
        return $members
    }
}