Public/Get-AzPolicyBaseline.ps1

function Get-AzPolicyBaseline {
    <#
    .SYNOPSIS
        Retrieve Azure Policy baseline from ALZ and/or MCSB sources.
     
    .DESCRIPTION
        Downloads and parses Azure Landing Zones (ALZ) baseline from AzAdvertizer CSV,
        optionally combines with Microsoft Cloud Security Benchmark (MCSB) from Azure.
         
        Returns a consolidated baseline with deduplication and source tracking.
     
    .PARAMETER IncludeAlz
        Include Azure Landing Zones baseline policies.
        Default: $true
     
    .PARAMETER IncludeMcsb
        Include Microsoft Cloud Security Benchmark policies.
        Default: $true
     
    .PARAMETER CsvPath
        Path to local AzAdvertizer CSV file (optional).
        If not specified, will download from CsvUrl.
     
    .PARAMETER CsvUrl
        URL to download AzAdvertizer CSV (optional).
        Default: Uses predefined AzAdvertizer URL.
     
    .PARAMETER SelectedAlzInitiatives
        Filter specific ALZ initiatives by name pattern.
        Empty array = include all initiatives.
        Example: @("alzroot", "alz-Identity")
     
    .PARAMETER CacheMaxAgeHours
        Maximum age in hours for cached CSV file.
        Default: 24 hours
     
    .EXAMPLE
        # Get both ALZ and MCSB baselines
        $baseline = Get-AzPolicyBaseline -IncludeAlz -IncludeMcsb
         
        Write-Host "Total policies in baseline: $($baseline.Policies.Count)"
        Write-Host "ALZ policies: $(($baseline.Policies | Where-Object { $_.BaselineSources -like '*ALZ*' }).Count)"
        Write-Host "MCSB policies: $(($baseline.Policies | Where-Object { $_.BaselineSources -like '*MCSB*' }).Count)"
     
    .EXAMPLE
        # Get only ALZ baseline with specific initiatives
        $baseline = Get-AzPolicyBaseline -IncludeAlz -IncludeMcsb:$false `
                                         -SelectedAlzInitiatives @("alzroot", "alz-Identity")
     
    .EXAMPLE
        # Use custom CSV path
        $baseline = Get-AzPolicyBaseline -IncludeAlz -CsvPath "C:\Data\policies.csv"
     
    .OUTPUTS
        PSCustomObject with properties:
        - Policies: Array of policy objects with properties:
          * PolicyDisplayName: Display name of the policy
          * PolicyDefinitionId: Full Azure resource ID
          * Version: Policy version (e.g., "1.0.0")
          * Effect: Default effect (Audit, Deny, etc.)
          * Category: Policy category
          * Type: "Policy" or "PolicySet"
          * BaselineSources: "ALZ", "MCSB", or "ALZ, MCSB"
         
        - Index: PSCustomObject with hashtable indexes:
          * ById: Policies indexed by PolicyDefinitionId
          * ByName: Policies indexed by exact PolicyDisplayName
          * ByNormName: Policies indexed by normalized name
     
    .NOTES
        Requires Az.Resources module for MCSB baseline retrieval.
        ALZ baseline is retrieved from AzAdvertizer CSV (no Az module required).
    #>

    
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [switch]$IncludeAlz = $true,
        [switch]$IncludeMcsb = $true,
        
        [string]$CsvPath,
        [string]$CsvUrl,
        [string[]]$SelectedAlzInitiatives = @(),
        
        [int]$CacheMaxAgeHours = 24
    )
    
    begin {
        Write-Debug "Get-AzPolicyBaseline: Starting baseline retrieval"
        Write-Debug " IncludeAlz: $IncludeAlz"
        Write-Debug " IncludeMcsb: $IncludeMcsb"
        
        if (-not $IncludeAlz -and -not $IncludeMcsb) {
            throw "At least one of -IncludeAlz or -IncludeMcsb must be specified"
        }
    }
    
    process {
        try {
            $alzBaseline = @()
            $mcsbBaseline = @()
            
            # Step 1: Get ALZ baseline if requested
            if ($IncludeAlz) {
                Write-Verbose "Retrieving ALZ baseline from AzAdvertizer..."
                
                # Download or load CSV
                if ($CsvPath -and (Test-Path $CsvPath)) {
                    Write-Verbose " Using local CSV: $CsvPath"
                    $polCsv = Import-CsvWithFallback -Path $CsvPath
                } else {
                    # Use Get-AzAdvertizerCsv with caching
                    $defaultUrl = "https://www.azadvertizer.net/azpolicyadvertizer-comma.csv"
                    $url = if ($CsvUrl) { $CsvUrl } else { $defaultUrl }
                    
                    $cacheFolder = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) "AzurePolicyWatch\Cache"
                    if (-not (Test-Path $cacheFolder)) {
                        New-Item -Path $cacheFolder -ItemType Directory -Force | Out-Null
                    }
                    
                    $cachePath = Join-Path $cacheFolder "azpolicyadvertizer-comma.csv"
                    
                    Write-Verbose " Downloading from: $url"
                    $csvPath = Get-AzAdvertizerCsv -Url $url -CachePath $cachePath -MaxAgeHours $CacheMaxAgeHours
                    $polCsv = Import-CsvWithFallback -Path $csvPath
                }
                
                if (-not $polCsv) {
                    throw "Failed to retrieve AzAdvertizer CSV"
                }
                
                Write-Verbose " CSV loaded: $($polCsv.Count) total rows"
                
                # Detect ALZ initiatives
                $alzInitiatives = Get-AlzInitiatives -CsvData $polCsv
                Write-Verbose " ALZ initiatives found: $($alzInitiatives.Count)"
                
                # Import ALZ baseline
                $alzBaseline = Import-AlzBaseline -CsvData $polCsv -AlzInitiatives $alzInitiatives -SelectedInitiatives $SelectedAlzInitiatives
                Write-Verbose " ALZ baseline policies: $($alzBaseline.Count)"
            }
            
            # Step 2: Get MCSB baseline if requested
            if ($IncludeMcsb) {
                Write-Verbose "Retrieving MCSB baseline from Azure..."
                
                # Verify Az context
                try {
                    $context = Get-AzContext
                    if (-not $context) {
                        Write-Warning "No Azure context found. MCSB baseline requires authentication."
                        Write-Warning "Run Connect-AzAccount to authenticate."
                        throw "Azure authentication required for MCSB baseline"
                    }
                } catch {
                    throw "Failed to get Azure context: $($_.Exception.Message)"
                }
                
                # Use module-scoped caches (persistent across calls)
                $mcsbBaseline = Import-McsbBaseline -InitiativeDisplayName "Microsoft cloud security benchmark" `
                                                     -PolicyDefinitionCache $script:PolicyDefinitionCache `
                                                     -PolicySetDefinitionCache $script:PolicySetCache
                
                Write-Verbose " MCSB baseline policies: $($mcsbBaseline.Count)"
            }
            
            # Step 3: Merge baselines
            Write-Verbose "Merging baselines..."
            $baseline = Merge-PolicyBaselines -AlzBaseline $alzBaseline -McsbBaseline $mcsbBaseline
            Write-Verbose " Merged baseline: $($baseline.Count) policies"
            
            # Step 4: Create indexes
            Write-Verbose "Creating baseline indexes..."
            $baselineIndex = New-BaselineIndex -Baseline $baseline
            
            # Return result
            $result = [PSCustomObject]@{
                Policies = $baseline
                Index = $baselineIndex
            }
            
            Write-Debug "Get-AzPolicyBaseline: Completed successfully"
            return $result
            
        } catch {
            Write-Error "Failed to retrieve policy baseline: $($_.Exception.Message)"
            throw
        }
    }
}