Public/Invoke-AzRoleAccessGuardDrifft.ps1

# Loads 'MyFunctions.ps1' from the same folder as the current script
# . "D:\Git\PowerShell.IGA.Tool\src\PowerShell.IGA.Tool\Private\MyTesFunction.ps1"

# Get-WelcomeMessage -Name "Alice"

# Feature add switch for MG / Subscription export
# Default Export is both MG and Subscription
# Change Name of output
# change of functions
# Add Private function to get role assignments for MG and Subscription

#Requires –Modules Az

function Invoke-AzRoleAccessGuardDrifft {
    <#
    .SYNOPSIS
        Compares the current Azure role assignments against a previously exported reference file and reports drift.
    #>

    [CmdletBinding()]
    param(
        # Previously exported role assignment file (e.g. produced by Export-AzRoleAssignment) to compare against
        [string]$ReferenceFile,

        # Where the detected differences are written, without extension; the correct extension is appended based on -OutputFormat.
        [string]$OutputPath = (Join-Path (Split-Path -Parent $PSScriptRoot) 'output_diff'),

        # Output format for the drift report. 'Terminal' prints a table to the host instead of writing a file. Defaults to Json.
        [ValidateSet('Terminal', 'Json', 'Html', 'Csv', 'JUnit', 'Bicep')]
        [string]$OutputFormat = 'Json'
    )

    $report = Get-AzRoleAssignmentReport
    $role_assigment_data_subscriptions = $report.Subscriptions
    $role_assigment_data_management_groups = $report.ManagementGroups

    $role_assigment_export = [PSCustomObject]@{
        ManagementGroup = [PSCustomObject]$role_assigment_data_management_groups
        Subscription = [PSCustomObject]$role_assigment_data_subscriptions
    }

    if (-not (Test-Path -Path $ReferenceFile)) {
        Write-Warning "Reference file '$ReferenceFile' not found. Skipping drift comparison."
        return $role_assigment_export
    }

    $reference_export = Get-Content -Path $ReferenceFile -Raw | ConvertFrom-Json
    $reference_export = ConvertFrom-RoleAssignmentExportDocument -ExportData $reference_export

    $drift_result = [ordered]@{
        ManagementGroup = [ordered]@{}
        Subscription = [ordered]@{}
    }

    $scope_test_results = @()

    foreach ($section in 'ManagementGroup', 'Subscription') {

        $current_section = $role_assigment_export.$section
        $reference_section = $reference_export.$section

        $current_ids = @()
        if ($current_section) { $current_ids = @($current_section.PSObject.Properties.Name) }

        $reference_ids = @()
        if ($reference_section) { $reference_ids = @($reference_section.PSObject.Properties.Name) }

        $all_ids = @($current_ids + $reference_ids) | Select-Object -Unique

        foreach ($id in $all_ids) {

            $current_assignments = @()
            if ($current_ids -contains $id) { $current_assignments = @($current_section.$id) }

            $reference_assignments = @()
            if ($reference_ids -contains $id) { $reference_assignments = @($reference_section.$id) }

            $id_diff = Compare-AzRoleAssignmentSet -ReferenceAssignments $reference_assignments -CurrentAssignments $current_assignments

            $scope_test_results += [PSCustomObject]@{
                Section = $section
                ScopeId = $id
                Diff    = $id_diff
            }

            if ($id_diff.Count -gt 0) {
                $drift_result[$section][$id] = $id_diff
            }
        }
    }

    $drift_output = [PSCustomObject]@{
        ManagementGroup = [PSCustomObject]$drift_result.ManagementGroup
        Subscription = [PSCustomObject]$drift_result.Subscription
    }

    $drift_documents = ConvertTo-RoleAssignmentDriftDocuments -DriftResult $drift_result

    switch ($OutputFormat) {
        'Terminal' {
            New-FlatDriftResultList -DriftResult $drift_result | Format-Table -AutoSize
        }
        'Json' {
            $drift_documents | ConvertTo-Json -Depth 6 | Out-File -FilePath "$OutputPath.json" -Encoding utf8
                Write-Host "Drift report written to $OutputPath.json"
        }
        'Html' {
            ConvertTo-DriftGroupedHtmlReport -Title 'Azure Role Assignment Drift Report' -DriftResult $drift_result -ManagementGroupNames $report.ManagementGroupNames -SubscriptionNames $report.SubscriptionNames -GeneratedBy 'Test-AzRoleAssignment' | Out-File -FilePath "$OutputPath.html" -Encoding utf8
            Write-Host "Drift report written to $OutputPath.html"
        }
        'Csv' {
            New-FlatDriftResultList -DriftResult $drift_result | Export-Csv -Path "$OutputPath.csv" -NoTypeInformation -Encoding utf8
            Write-Host "Drift report written to $OutputPath.csv"
        }
        'JUnit' {
            ConvertTo-DriftJUnitXml -ScopeResults $scope_test_results | Out-File -FilePath "$OutputPath.xml" -Encoding utf8
            Write-Host "Drift report written to $OutputPath.xml"
        }
        'Bicep' {
            $bicep_management_group_data = [ordered]@{}
            $bicep_subscription_data = [ordered]@{}
            $removal_count = 0

            foreach ($section in 'ManagementGroup', 'Subscription') {
                foreach ($scope_id in $drift_result[$section].Keys) {
                    $scope_changes = @($drift_result[$section][$scope_id])
                    $removal_count += @($scope_changes | Where-Object { $_.Status -eq 'Added' }).Count
                    $assignments_to_add = @($scope_changes | Where-Object { $_.Status -eq 'Removed' } | ForEach-Object { $_.Assignment })

                    if ($assignments_to_add.Count -gt 0) {
                        if ($section -eq 'ManagementGroup') {
                            $bicep_management_group_data[$scope_id] = $assignments_to_add
                        }
                        else {
                            $bicep_subscription_data[$scope_id] = $assignments_to_add
                        }
                    }
                }
            }

            $bicep_export = ConvertTo-RoleAssignmentBicep -SubscriptionData $bicep_subscription_data -ManagementGroupData $bicep_management_group_data
            $bicep_export | Out-File -FilePath "$OutputPath.bicep" -Encoding utf8
            Write-Host "Bicep drift deployment written to $OutputPath.bicep"
            if ($removal_count -gt 0) {
                Write-Warning "$removal_count added role assignment(s) require removal. The AVM Bicep modules only create assignments, so these changes are not included in the Bicep output."
            }
        }
    }

    $total_changes = ($drift_result.ManagementGroup.Values + $drift_result.Subscription.Values | ForEach-Object { $_.Count } | Measure-Object -Sum).Sum
    Write-Host "Drift detection complete. $total_changes change(s) found."

    return $drift_output

}