Public/New-DLPReport.ps1

function New-DLPReport {
    <#
    .SYNOPSIS
        Generates comprehensive HTML reports from DLP policy configurations.
    
    .DESCRIPTION
        Creates formatted reports showing all DLP policies and their rules in HTML format.
        Reports include interactive collapsible sections for easy navigation.
        
        Reports include:
        - Policy overview with statistics
        - Location scope information with accurate "All" vs specific detection
        - Cross-tenant migration metadata (portability analysis)
        - Detailed policy configurations
        - Rule details with SIT configurations
        - Notification and incident reporting settings
        - Advanced Rule conditions (JSON-based rules)
        
        Can generate reports directly from live policies or from backup JSON files.
    
    .PARAMETER BackupFile
        Optional. Path to a JSON backup file created by Export-DLPConfiguration.
        If not provided, reports on currently configured policies (requires connection).
    
    .PARAMETER OutputPath
        Optional. Path where the report file will be saved.
        Default: [WorkspaceRoot]/Output/dlp-report-[timestamp].html
    
    .PARAMETER IncludeDisabledPolicies
        Include disabled policies in the report.
        Default: $true
    
    .PARAMETER IncludeDisabledRules
        Include disabled rules in the report.
        Default: $true
    
    .PARAMETER Title
        Custom title for the report.
        Default: "Microsoft Purview DLP Policy Report"
    
    .OUTPUTS
        String. Path to the generated report file.
    
    .EXAMPLE
        New-DLPReport
        
        Generate report from currently configured policies (requires connection).
    
    .EXAMPLE
        New-DLPReport -BackupFile ".\Output\backup.json"
        
        Generate report from a backup JSON file.
    
    .EXAMPLE
        New-DLPReport -OutputPath "C:\Reports\dlp-policies.html"
        
        Generate report with custom output path.
    
    .EXAMPLE
        New-DLPReport -BackupFile ".\Output\backup.json" -IncludeDisabledPolicies $false
        
        Generate report excluding disabled policies.
    
    .EXAMPLE
        New-DLPReport -Title "Production DLP Configuration"
        
        Generate report with custom title.
    
    .NOTES
        Requires: Active connection to Security & Compliance Center (use Connect-PurviewDLP first)
                  unless using -BackupFile parameter
        Author: PurviewDLP Module
        
        Report Features:
        - Interactive collapsible policy sections
        - Color-coded policy modes (Enforce, Test, Disable)
        - Cross-tenant portability indicators
        - Location scope analysis (All vs Specific)
        - Rule priority and action summaries
        - SIT configuration details
        - Advanced Rule condition parsing
        - Distribution status tracking
    
    .LINK
        https://github.com/uniQuk/PurviewDLP
    #>

    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false, Position = 0)]
        [string]$BackupFile,
        
        [Parameter(Mandatory = $false)]
        [string]$OutputPath,
        
        [Parameter(Mandatory = $false)]
        [bool]$IncludeDisabledPolicies = $true,
        
        [Parameter(Mandatory = $false)]
        [bool]$IncludeDisabledRules = $true,
        
        [Parameter(Mandatory = $false)]
        [string]$Title = "Microsoft Purview DLP Policy Report"
    )
    
    begin {
        Write-Verbose "Starting New-DLPReport"
        
        # Resolve output path
        if (-not $OutputPath) {
            $timestamp = Get-Date -Format "yyyy-MM-dd_HHmmss"
            $defaultPath = Get-DefaultOutputPath -OutputPath "."
            $OutputPath = Join-Path $defaultPath "dlp-report-$timestamp.html"
        }
        
        Write-Verbose "Output path: $OutputPath"
    }
    
    process {
        try {
            # Display banner
            Write-Banner -Message "DLP Policy Report Generation" -Type "Info"
            
            # Load policy data
            if ($BackupFile) {
                Write-ColorOutput "Loading policies from backup file: $BackupFile" -Type Info
                
                if (-not (Test-Path $BackupFile)) {
                    throw "Backup file not found: $BackupFile"
                }
                
                try {
                    $policiesData = Get-Content $BackupFile -Raw -Encoding UTF8 | ConvertFrom-Json
                }
                catch {
                    throw "Failed to parse backup file as JSON: $($_.Exception.Message)"
                }
                
                # Ensure array
                $policies = if ($policiesData -is [array]) { $policiesData } else { @($policiesData) }
                
                Write-ColorOutput "✓ Loaded $($policies.Count) policy/policies from backup`n" -Type Success
            }
            else {
                Write-ColorOutput "Retrieving policies from Security & Compliance Center..." -Type Info
                Test-DLPConnection  # Throws if not connected
                
                # Get live policies using the Export cmdlet logic
                Write-ColorOutput "Fetching DLP policies..." -Type Info
                $dlpPolicies = Get-DlpCompliancePolicy -ErrorAction Stop
                
                Write-ColorOutput "Found $($dlpPolicies.Count) policy/policies" -Type Info
                Write-ColorOutput "Processing policy details...`n" -Type Info
                
                # Process each policy (reuse logic from Export-DLPConfiguration)
                $policies = @()
                $counter = 0
                foreach ($policy in $dlpPolicies) {
                    $counter++
                    Write-Verbose "[$counter/$($dlpPolicies.Count)] Processing: $($policy.Name)"
                    
                    # Use the same internal function from Export-DLPConfiguration
                    # For simplicity, we'll fetch basic details here
                    $policyInfo = [PSCustomObject]@{
                        PolicyName = $policy.Name
                        PolicyId = $policy.Guid
                        Mode = $policy.Mode
                        Enabled = $policy.Enabled
                        CreatedBy = $policy.CreatedBy
                        CreatedDateTime = $policy.WhenCreated
                        ModifiedBy = $policy.LastModifiedBy
                        ModifiedDateTime = $policy.WhenChanged
                        Comment = $policy.Comment
                        Workload = $policy.Workload -join ", "
                        
                        # Cross-tenant metadata (simplified)
                        HasSpecificTargeting = $false  # Would need full analysis
                        HasLocationExceptions = $false
                        IsCrossTenantPortable = $true
                        
                        # Location info (simplified for report)
                        ExchangeLocation = $policy.ExchangeLocation -join ", "
                        ExchangeLocationIsAll = ($policy.ExchangeLocation -contains "All")
                        SharePointLocation = $policy.SharePointLocation -join ", "
                        SharePointLocationIsAll = ($policy.SharePointLocation -contains "All")
                        OneDriveLocation = $policy.OneDriveLocation -join ", "
                        OneDriveLocationIsAll = ($policy.OneDriveLocation -contains "All")
                        TeamsLocation = $policy.TeamsLocation -join ", "
                        TeamsLocationIsAll = ($policy.TeamsLocation -contains "All")
                        EndpointDlpLocation = $policy.EndpointDlpLocation -join ", "
                        EndpointDlpLocationIsAll = ($policy.EndpointDlpLocation -contains "All")
                        
                        # Exceptions
                        ExchangeLocationException = $policy.ExchangeLocationException -join ", "
                        SharePointLocationException = $policy.SharePointLocationException -join ", "
                        OneDriveLocationException = $policy.OneDriveLocationException -join ", "
                        TeamsLocationException = $policy.TeamsLocationException -join ", "
                        
                        DistributionStatus = $policy.DistributionStatus
                        Priority = $policy.Priority
                        
                        RuleCount = 0
                        Rules = @()
                    }
                    
                    # Get rules
                    try {
                        $rules = Get-DlpComplianceRule -Policy $policy.Name -ErrorAction Stop
                        $policyInfo.RuleCount = ($rules | Measure-Object).Count
                        
                        foreach ($rule in $rules) {
                            $policyInfo.Rules += [PSCustomObject]@{
                                RuleName = $rule.Name
                                RuleId = $rule.Guid
                                PolicyName = $policy.Name
                                Disabled = $rule.Disabled
                                Mode = $rule.Mode
                                Priority = $rule.Priority
                                
                                AdvancedRule = $rule.AdvancedRule
                                
                                ContentContainsSensitiveInformation = if ($rule.ContentContainsSensitiveInformation) { 
                                    ($rule.ContentContainsSensitiveInformation | ConvertTo-Json -Depth 10 -Compress)
                                } else { $null }
                                
                                NotifyUser = $rule.NotifyUser -join "; "
                                NotifyUserType = $rule.NotifyUserType
                                NotifyAllowOverride = $rule.NotifyAllowOverride -join "; "
                                NotifyPolicyTipCustomText = $rule.NotifyPolicyTipCustomText
                                NotifyEmailCustomText = $rule.NotifyEmailCustomText
                                
                                GenerateIncidentReport = $rule.GenerateIncidentReport -join "; "
                                ReportSeverityLevel = $rule.ReportSeverityLevel
                                
                                BlockAccess = $rule.BlockAccess
                                BlockAccessScope = $rule.BlockAccessScope
                                EncryptRMSTemplate = $rule.EncryptRMSTemplate
                                
                                CreatedBy = $rule.CreatedBy
                                CreatedDateTime = $rule.WhenCreated
                                ModifiedBy = $rule.LastModifiedBy
                                ModifiedDateTime = $rule.WhenChanged
                                Comment = $rule.Comment
                            }
                        }
                    }
                    catch {
                        Write-Verbose " Warning: Could not retrieve rules for policy '$($policy.Name)'"
                    }
                    
                    $policies += $policyInfo
                }
                
                Write-ColorOutput "✓ Processed $($policies.Count) policies`n" -Type Success
            }
            
            # Filter policies if needed
            if (-not $IncludeDisabledPolicies) {
                $originalCount = $policies.Count
                $policies = $policies | Where-Object { $_.Enabled -eq $true }
                Write-ColorOutput "Filtered out $($originalCount - $policies.Count) disabled policy/policies" -Type Info
            }
            
            # Filter rules if needed
            if (-not $IncludeDisabledRules) {
                foreach ($policy in $policies) {
                    $originalRuleCount = $policy.Rules.Count
                    $policy.Rules = $policy.Rules | Where-Object { $_.Disabled -ne $true }
                    if ($originalRuleCount -ne $policy.Rules.Count) {
                        Write-Verbose " Filtered $($originalRuleCount - $policy.Rules.Count) disabled rules from policy '$($policy.PolicyName)'"
                    }
                }
            }
            
            # Generate statistics
            Write-ColorOutput "Generating report statistics..." -Type Info
            $stats = Get-PolicyStatisticsInternal -Policies $policies
            
            # Build HTML report
            Write-ColorOutput "Building HTML report..." -Type Info
            $reportHtml = Build-ReportHTML -Policies $policies -Statistics $stats -ReportTitle $Title
            
            # Save report
            Write-ColorOutput "Saving report to: $OutputPath" -Type Info
            
            # Ensure output directory exists
            $outputDir = Split-Path $OutputPath -Parent
            if ($outputDir -and -not (Test-Path $outputDir)) {
                New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
            }
            
            $reportHtml | Out-File -FilePath $OutputPath -Encoding UTF8 -Force
            
            # Display summary
            Write-Banner -Message "Report Summary" -Type "Info"
            
            Write-ColorOutput "Report Statistics:" -Type Info
            Write-Host " Total policies: $($stats.TotalPolicies)"
            Write-Host " Enabled policies: $($stats.EnabledPolicies)"
            Write-Host " Disabled policies: $($stats.DisabledPolicies)"
            Write-Host " Total rules: $($stats.TotalRules)"
            Write-Host " Enforce mode: $($stats.EnforceMode)"
            Write-Host " Test mode: $($stats.TestMode)"
            Write-Host ""
            
            Write-ColorOutput "Report saved:" -Type Success
            Write-Host " Location: $OutputPath" -ForegroundColor Green
            Write-Host " Size: $([Math]::Round((Get-Item $OutputPath).Length / 1KB, 2)) KB" -ForegroundColor Green
            Write-Host ""
            
            Write-ColorOutput "✓ Report generation completed successfully!" -Type Success
            Write-Host ""
            
            # Return report path
            return $OutputPath
        }
        catch {
            Write-Host ""
            Write-ColorOutput "✗ Error generating report:" -Type Error
            Write-ColorOutput $_.Exception.Message -Type Error
            Write-Verbose "Stack Trace: $($_.ScriptStackTrace)"
            
            Write-Host ""
            Write-ColorOutput "Troubleshooting tips:" -Type Warning
            Write-ColorOutput " 1. If not using -BackupFile, ensure you are connected: Connect-PurviewDLP" -Type Info
            Write-ColorOutput " 2. Verify the backup file path is correct (if using -BackupFile)" -Type Info
            Write-ColorOutput " 3. Check that the output directory is writable" -Type Info
            Write-ColorOutput " 4. Ensure the backup file contains valid JSON" -Type Info
            Write-ColorOutput " 5. Use -Verbose for detailed diagnostic output" -Type Info
            Write-Host ""
            
            throw
        }
    }
    
    end {
        Write-Verbose "New-DLPReport completed"
    }
}

#region Internal Helper Functions

function Get-PolicyStatisticsInternal {
    <#
    .SYNOPSIS
        Internal function to calculate policy statistics for report.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [array]$Policies
    )
    
    $stats = @{
        TotalPolicies = $Policies.Count
        EnabledPolicies = ($Policies | Where-Object { $_.Enabled -eq $true }).Count
        DisabledPolicies = ($Policies | Where-Object { $_.Enabled -eq $false }).Count
        TotalRules = ($Policies | ForEach-Object { $_.Rules.Count } | Measure-Object -Sum).Sum
        TestMode = ($Policies | Where-Object { $_.Mode -like "*Test*" }).Count
        EnforceMode = ($Policies | Where-Object { $_.Mode -eq "Enforce" }).Count
        PoliciesWithRules = ($Policies | Where-Object { $_.RuleCount -gt 0 }).Count
        PoliciesWithoutRules = ($Policies | Where-Object { $_.RuleCount -eq 0 }).Count
        Workloads = @{}
    }
    
    # Count workload distribution
    foreach ($policy in $Policies) {
        if ($policy.Workload) {
            $workloads = $policy.Workload -split ", " | Where-Object { $_ }
            foreach ($workload in $workloads) {
                if (-not $stats.Workloads.ContainsKey($workload)) {
                    $stats.Workloads[$workload] = 0
                }
                $stats.Workloads[$workload]++
            }
        }
    }
    
    return $stats
}

#endregion