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
    Category           = if ($policy.Metadata.category) { $policy.Metadata.category } else { "Uncategorized" }
}
        }
        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"
    Category           = "Uncategorized"
}
        }
    }
    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
    Category           = if ($pol.Metadata.category) { $pol.Metadata.category } else { "Uncategorized" }
}
            }
            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"
    Category           = "Uncategorized"
}
            }
        }
        
        return $members
    }
}