Public/Restore-DLPConfiguration.ps1

function Restore-DLPConfiguration {
    <#
    .SYNOPSIS
        Restores Microsoft Purview DLP policies from a backup JSON file.
    
    .DESCRIPTION
        Restores DLP policies and rules from a JSON backup created by Export-DLPConfiguration.
        Can restore individual policies or all policies from the backup.
        
        Supports:
        - Policy duplication (rename during restore)
        - Creating policies in disabled state for review
        - Cross-tenant migration with automatic location scope conversion
        - Skipping existing policies
        - Migration report generation (HTML)
        
        IMPORTANT: This is a RESTORE operation. Use with caution. Always use -WhatIf first.
    
    .PARAMETER BackupFile
        Required. Path to the JSON backup file created by Export-DLPConfiguration.
    
    .PARAMETER PolicyName
        Optional. Specific policy name to restore. If not provided, all policies are restored.
    
    .PARAMETER NewPolicyName
        Optional. Rename the policy during restore. Only works with -PolicyName.
        Useful for duplicating policies or avoiding naming conflicts.
    
    .PARAMETER CreateDisabled
        Create policies in disabled state (Enabled = $false) for inspection before activation.
        Recommended for cross-tenant migrations.
    
    .PARAMETER SkipExisting
        Skip policies that already exist instead of failing with an error.
    
    .PARAMETER CrossTenantMode
        Enable cross-tenant migration mode. This mode:
        - Converts specific user/group/site targeting to 'All' locations
        - Removes location exceptions (not portable across tenants)
        - Generates a migration report documenting all changes
        
        Use when migrating policies from one Microsoft 365 tenant to another.
    
    .PARAMETER MigrationReportPath
        Optional. Custom path for the migration report (HTML format).
        Default: [WorkspaceRoot]/Output/dlp-migration-report-[timestamp].html
        
        Report includes details of all location scope changes and warnings.
    
    .PARAMETER WhatIf
        Preview what would be restored without making any changes.
        Always recommended before running the actual restore.
    
    .PARAMETER Confirm
        Prompt for confirmation before restoring each policy.
    
    .OUTPUTS
        PSCustomObject with restoration statistics:
        - TotalPolicies: Number of policies in backup
        - RestoredPolicies: Number successfully restored
        - SkippedPolicies: Number skipped (already exist)
        - FailedPolicies: Number that failed
    
    .EXAMPLE
        Restore-DLPConfiguration -BackupFile ".\Output\backup.json" -WhatIf
        
        Preview restoration of all policies from backup without making changes.
    
    .EXAMPLE
        Restore-DLPConfiguration -BackupFile ".\Output\backup.json" -PolicyName "GDPR Enhanced"
        
        Restore a specific policy from backup.
    
    .EXAMPLE
        Restore-DLPConfiguration -BackupFile ".\Output\backup.json" -PolicyName "GDPR Enhanced" -NewPolicyName "GDPR Enhanced - Copy"
        
        Duplicate a policy with a new name.
    
    .EXAMPLE
        Restore-DLPConfiguration -BackupFile ".\Output\backup.json" -CreateDisabled
        
        Restore all policies in disabled state for inspection.
    
    .EXAMPLE
        Restore-DLPConfiguration -BackupFile ".\Output\backup.json" -SkipExisting
        
        Restore all policies, skipping any that already exist.
    
    .EXAMPLE
        Restore-DLPConfiguration -BackupFile ".\Output\backup.json" -CrossTenantMode -CreateDisabled
        
        Migrate policies from another tenant:
        - Converts specific targeting to 'All' locations
        - Removes exceptions
        - Creates policies disabled for review
        - Generates migration report
    
    .NOTES
        Requires: Active connection to Security & Compliance Center (use Connect-PurviewDLP first)
        Author: PurviewDLP Module
        
        Cross-Tenant Migration Workflow:
        1. Connect to source tenant
        2. Export backup: Export-DLPConfiguration
        3. Connect to destination tenant
        4. Restore with -CrossTenantMode -CreateDisabled -WhatIf (preview)
        5. Restore with -CrossTenantMode -CreateDisabled (create disabled)
        6. Review migration report for changes
        7. Review policies in Microsoft Purview UI
        8. Enable policies when ready using Set-DlpCompliancePolicy
        
        Location Scope Conversion (Cross-Tenant Mode):
        - Specific users/groups → 'All'
        - Specific sites → 'All'
        - Location exceptions → Removed
        - Adaptive scopes → Preserved (must exist in target tenant)
    
    .LINK
        https://github.com/uniQuk/PurviewDLP
    #>

    
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateScript({ Test-Path $_ -PathType Leaf })]
        [string]$BackupFile,
        
        [Parameter(Mandatory = $false)]
        [string]$PolicyName,
        
        [Parameter(Mandatory = $false)]
        [string]$NewPolicyName,
        
        [Parameter(Mandatory = $false)]
        [switch]$CreateDisabled,
        
        [Parameter(Mandatory = $false)]
        [switch]$SkipExisting,
        
        [Parameter(Mandatory = $false)]
        [switch]$CrossTenantMode,
        
        [Parameter(Mandatory = $false)]
        [string]$MigrationReportPath
    )
    
    begin {
        Write-Verbose "Starting Restore-DLPConfiguration"
        
        # Validate parameter combinations
        if ($NewPolicyName -and -not $PolicyName) {
            throw "Parameter -NewPolicyName requires -PolicyName to be specified"
        }
        
        # Initialize statistics
        $script:stats = @{
            TotalPolicies = 0
            RestoredPolicies = 0
            SkippedPolicies = 0
            FailedPolicies = 0
        }
        
        # Initialize migration report if needed
        $script:migrationReport = $null
        if ($CrossTenantMode) {
            $script:migrationReport = [PSCustomObject]@{
                MigrationDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
                SourceBackupFile = (Resolve-Path $BackupFile).Path
                CrossTenantMode = $true
                CreateDisabled = $CreateDisabled
                PoliciesProcessed = @()
                TotalPolicies = 0
                PoliciesWithChanges = 0
                Warnings = @()
            }
        }
    }
    
    process {
        try {
            # Display banner
            Write-Banner -Message "DLP Configuration Restoration" -Type "Info"
            
            # Verify connection
            Write-ColorOutput "Checking connection to Security & Compliance Center..." -Type Info
            Test-DLPConnection  # Throws if not connected
            Write-ColorOutput "✓ Connected successfully`n" -Type Success
            
            # Load backup file
            Write-ColorOutput "Loading backup file: $BackupFile" -Type Info
            
            if (-not (Test-Path $BackupFile)) {
                throw "Backup file not found: $BackupFile"
            }
            
            try {
                $backupContent = Get-Content $BackupFile -Raw -Encoding UTF8 | ConvertFrom-Json
            }
            catch {
                throw "Failed to parse backup file as JSON: $($_.Exception.Message)"
            }
            
            # Ensure backup content is an array
            $allPolicies = if ($backupContent -is [array]) { $backupContent } else { @($backupContent) }
            
            Write-ColorOutput "✓ Loaded $($allPolicies.Count) policy/policies from backup`n" -Type Success
            
            # Filter policies if specific policy requested
            if ($PolicyName) {
                $policiesToRestore = $allPolicies | Where-Object { $_.PolicyName -eq $PolicyName }
                
                if ($policiesToRestore.Count -eq 0) {
                    throw "Policy '$PolicyName' not found in backup file"
                }
                
                Write-ColorOutput "Filtered to policy: $PolicyName`n" -Type Info
            }
            else {
                $policiesToRestore = $allPolicies
            }
            
            $script:stats.TotalPolicies = $policiesToRestore.Count
            
            if ($script:migrationReport) {
                $script:migrationReport.TotalPolicies = $script:stats.TotalPolicies
            }
            
            # Display mode information
            if ($CrossTenantMode) {
                Write-ColorOutput "⚠️ CROSS-TENANT MIGRATION MODE ENABLED" -Type Warning
                Write-ColorOutput " - Specific user/group/site targeting will be converted to 'All'" -Type Warning
                Write-ColorOutput " - Location exceptions will be removed" -Type Warning
                Write-ColorOutput " - A migration report will be generated`n" -Type Warning
            }
            
            if ($CreateDisabled) {
                Write-ColorOutput "ℹ️ Policies will be created in DISABLED state for review`n" -Type Info
            }
            
            # Check WhatIf mode
            $whatIfMode = $WhatIfPreference.IsPresent
            if ($whatIfMode) {
                Write-ColorOutput "⚠️ PREVIEW MODE: No changes will be applied`n" -Type Warning
            }
            
            # Confirmation prompt (unless WhatIf)
            if (-not $whatIfMode) {
                $message = "This will restore $($script:stats.TotalPolicies) policy/policies from backup."
                if ($CrossTenantMode) {
                    $message += "`n ⚠️ Cross-tenant mode will modify location scopes."
                }
                
                $question = "Do you want to continue?"
                $choices = @(
                    [System.Management.Automation.Host.ChoiceDescription]::new("&Yes", "Restore policies")
                    [System.Management.Automation.Host.ChoiceDescription]::new("&No", "Cancel operation")
                )
                
                $decision = $Host.UI.PromptForChoice($message, $question, $choices, 1)
                if ($decision -ne 0) {
                    Write-ColorOutput "`nOperation cancelled by user." -Type Warning
                    return
                }
                Write-Host ""
            }
            
            Write-Banner -Message "Restoration Progress" -Type "Info"
            
            # Restore each policy
            $counter = 0
            foreach ($policy in $policiesToRestore) {
                $counter++
                $displayName = if ($NewPolicyName) { $NewPolicyName } else { $policy.PolicyName }
                Write-ColorOutput "`n[$counter/$($script:stats.TotalPolicies)] Restoring policy: $displayName" -Type Info
                
                # Track migration changes
                $policyChanges = @{}
                
                $restoreParams = @{
                    PolicyData = $policy
                    OverrideName = $NewPolicyName
                    WhatIf = $whatIfMode
                }
                
                if ($CrossTenantMode) {
                    $restoreParams.MigrationChanges = [ref]$policyChanges
                }
                
                $success = Restore-PolicyInternal @restoreParams
                
                if ($success) {
                    $script:stats.RestoredPolicies++
                    
                    # Add to migration report
                    if ($script:migrationReport) {
                        Add-PolicyMigrationDetailsInternal -Report $script:migrationReport -PolicyData $policy -Changes $policyChanges
                    }
                }
                elseif ($SkipExisting) {
                    $script:stats.SkippedPolicies++
                }
                else {
                    $script:stats.FailedPolicies++
                }
            }
            
            # Generate migration report
            if ($script:migrationReport -and -not $whatIfMode -and $script:stats.RestoredPolicies -gt 0) {
                Write-Host ""
                Write-ColorOutput "Generating migration report..." -Type Info
                
                # Resolve report path
                if (-not $MigrationReportPath) {
                    $timestamp = Get-Date -Format "yyyy-MM-dd_HHmmss"
                    $reportPath = Get-DefaultOutputPath -OutputPath (Join-Path "." "Output")
                    $MigrationReportPath = Join-Path $reportPath "dlp-migration-report-$timestamp.html"
                }
                
                Export-MigrationReportInternal -Report $script:migrationReport -OutputPath $MigrationReportPath
                Write-ColorOutput "✓ Migration report saved: $MigrationReportPath" -Type Success
            }
            
            # Display summary
            Write-Banner -Message "Restoration Summary" -Type "Info"
            
            Write-ColorOutput "Statistics:" -Type Info
            Write-Host " Total policies in backup: $($script:stats.TotalPolicies)"
            
            if ($whatIfMode) {
                Write-ColorOutput " Policies that would be restored: $($script:stats.RestoredPolicies)" -Type Warning
            }
            else {
                Write-ColorOutput " Policies successfully restored: $($script:stats.RestoredPolicies)" -Type Success
            }
            
            if ($script:stats.SkippedPolicies -gt 0) {
                Write-Host " Policies skipped (already exist): $($script:stats.SkippedPolicies)"
            }
            if ($script:stats.FailedPolicies -gt 0) {
                Write-ColorOutput " Policies failed: $($script:stats.FailedPolicies)" -Type Error
            }
            
            Write-Host ""
            if ($whatIfMode) {
                Write-ColorOutput "✓ Preview completed. To restore, run without -WhatIf parameter." -Type Warning
            }
            else {
                if ($script:stats.FailedPolicies -eq 0 -and $script:stats.RestoredPolicies -gt 0) {
                    Write-ColorOutput "✓ Restoration completed successfully!" -Type Success
                    
                    if ($CreateDisabled) {
                        Write-Host ""
                        Write-ColorOutput "ℹ️ Policies were created in DISABLED state." -Type Info
                        Write-ColorOutput " Review them in Microsoft Purview and enable when ready." -Type Info
                    }
                    
                    if ($CrossTenantMode) {
                        Write-Host ""
                        Write-ColorOutput "ℹ️ Cross-tenant migration completed. Review the migration report:" -Type Info
                        Write-ColorOutput " $MigrationReportPath" -Type Info
                    }
                }
                elseif ($script:stats.RestoredPolicies -eq 0) {
                    Write-ColorOutput "⚠ No policies were restored. Check filters and existing policies." -Type Warning
                }
                else {
                    Write-ColorOutput "⚠ Restoration completed with errors. Review messages above." -Type Warning
                }
            }
            
            Write-Host ""
            
            # Return statistics object
            return [PSCustomObject]$script:stats
        }
        catch {
            Write-Host ""
            Write-ColorOutput "✗ Error during restoration:" -Type Error
            Write-ColorOutput $_.Exception.Message -Type Error
            Write-Verbose "Stack Trace: $($_.ScriptStackTrace)"
            
            Write-Host ""
            Write-ColorOutput "Troubleshooting tips:" -Type Warning
            Write-ColorOutput " 1. Ensure you are connected: Connect-PurviewDLP" -Type Info
            Write-ColorOutput " 2. Verify the backup file path is correct and contains valid JSON" -Type Info
            Write-ColorOutput " 3. Check that you have permissions to create DLP policies" -Type Info
            Write-ColorOutput " 4. Use -WhatIf to preview restoration before applying" -Type Info
            Write-ColorOutput " 5. Use -SkipExisting to skip policies that already exist" -Type Info
            Write-ColorOutput " 6. Use -Verbose for detailed diagnostic output" -Type Info
            Write-Host ""
            
            throw
        }
    }
    
    end {
        Write-Verbose "Restore-DLPConfiguration completed"
    }
}

#region Internal Helper Functions

function Restore-PolicyInternal {
    <#
    .SYNOPSIS
        Internal function to restore a single policy.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object]$PolicyData,
        
        [Parameter(Mandatory = $false)]
        [string]$OverrideName,
        
        [Parameter(Mandatory = $false)]
        [bool]$WhatIf = $false,
        
        [Parameter(Mandatory = $false)]
        [ref]$MigrationChanges
    )
    
    $policyName = if ($OverrideName) { $OverrideName } else { $PolicyData.PolicyName }
    
    try {
        # Check if policy already exists
        $existingPolicy = Get-DlpCompliancePolicy -Identity $policyName -ErrorAction SilentlyContinue
        
        if ($existingPolicy) {
            if ($SkipExisting) {
                Write-ColorOutput " ⚠️ Policy '$policyName' already exists - skipping" -Type Warning
                return $false
            }
            else {
                throw "Policy '$policyName' already exists. Use -SkipExisting to skip existing policies."
            }
        }
        
        if ($WhatIf) {
            Write-ColorOutput " [WHATIF] Would create policy: $policyName" -Type Warning
            Write-Verbose " Mode: $($PolicyData.Mode)"
            Write-Verbose " Enabled: $(if ($CreateDisabled) { '$false' } else { $PolicyData.Enabled })"
            Write-Verbose " Workload: $($PolicyData.Workload)"
            Write-Verbose " Rules: $($PolicyData.RuleCount)"
            
            if ($CrossTenantMode) {
                if ($PolicyData.HasSpecificTargeting) {
                    Write-ColorOutput " ⚠️ Will convert specific targeting to 'All'" -Type Warning
                }
                if ($PolicyData.HasLocationExceptions) {
                    Write-ColorOutput " ⚠️ Will remove location exceptions" -Type Warning
                }
            }
            
            return $true
        }
        
        # Build policy parameters
        $policyParams = @{
            Name = $policyName
            Mode = $PolicyData.Mode
            Comment = if ($PolicyData.Comment) { $PolicyData.Comment } else { "" }
        }
        
        # Process locations based on cross-tenant mode
        $locationProperties = @{
            Exchange = @{ Location = 'ExchangeLocation'; IsAll = 'ExchangeLocationIsAll'; Exception = 'ExchangeLocationException' }
            SharePoint = @{ Location = 'SharePointLocation'; IsAll = 'SharePointLocationIsAll'; Exception = 'SharePointLocationException' }
            OneDrive = @{ Location = 'OneDriveLocation'; IsAll = 'OneDriveLocationIsAll'; Exception = 'OneDriveLocationException' }
            Teams = @{ Location = 'TeamsLocation'; IsAll = 'TeamsLocationIsAll'; Exception = 'TeamsLocationException' }
            Endpoint = @{ Location = 'EndpointDlpLocation'; IsAll = 'EndpointDlpLocationIsAll'; Exception = $null }
        }
        
        foreach ($workload in $locationProperties.Keys) {
            $props = $locationProperties[$workload]
            $locationProp = $props.Location
            $isAllProp = $props.IsAll
            $exceptionProp = $props.Exception
            
            if ($PolicyData.$locationProp) {
                if ($CrossTenantMode) {
                    # Cross-tenant mode: Always use 'All' if location is configured
                    $policyParams[$locationProp] = "All"
                    
                    if ($PolicyData.$isAllProp -eq $false) {
                        # Track change
                        if ($MigrationChanges) {
                            $MigrationChanges.Value[$workload] = @{
                                Original = $PolicyData.$locationProp
                                New = "All"
                                Reason = "Specific targeting not portable across tenants"
                                ImpactLevel = "High"
                            }
                        }
                    }
                }
                else {
                    # Normal mode: Use actual values
                    if ($PolicyData.$isAllProp) {
                        $policyParams[$locationProp] = "All"
                    }
                    else {
                        $locations = $PolicyData.$locationProp
                        if ($locations -is [string]) {
                            $policyParams[$locationProp] = $locations -split ", " | Where-Object { $_ }
                        }
                        elseif ($locations -is [array]) {
                            $policyParams[$locationProp] = $locations
                        }
                    }
                }
                
                # Handle exceptions (only in non-cross-tenant mode)
                if ($exceptionProp -and $PolicyData.$exceptionProp -and -not $CrossTenantMode) {
                    $exceptions = $PolicyData.$exceptionProp
                    if ($exceptions -is [string]) {
                        $policyParams[$exceptionProp] = $exceptions -split ", " | Where-Object { $_ }
                    }
                    elseif ($exceptions -is [array]) {
                        $policyParams[$exceptionProp] = $exceptions
                    }
                }
                elseif ($exceptionProp -and $PolicyData.$exceptionProp -and $CrossTenantMode) {
                    # Track removed exceptions
                    if ($MigrationChanges) {
                        $MigrationChanges.Value["${workload}Exception"] = @{
                            Original = $PolicyData.$exceptionProp
                            New = "(removed)"
                            Reason = "Location exceptions not supported in cross-tenant migration"
                            ImpactLevel = "Medium"
                        }
                    }
                }
            }
        }
        
        # Create the policy
        Write-Verbose " Creating policy with parameters: $($policyParams.Keys -join ', ')"
        $newPolicy = New-DlpCompliancePolicy @policyParams -ErrorAction Stop
        Write-ColorOutput " ✓ Created policy: $policyName" -Type Success
        
        # Set enabled state
        if ($CreateDisabled -and $newPolicy.Enabled) {
            Set-DlpCompliancePolicy -Identity $newPolicy.Name -Enabled $false -ErrorAction Stop
            Write-Verbose " Disabled policy for review"
        }
        
        # Restore rules
        if ($PolicyData.Rules -and $PolicyData.Rules.Count -gt 0) {
            Write-ColorOutput " Restoring $($PolicyData.Rules.Count) rule(s)..." -Type Info
            
            foreach ($rule in $PolicyData.Rules) {
                Restore-RuleInternal -RuleData $rule -PolicyName $policyName -WhatIf $false
            }
        }
        
        return $true
    }
    catch {
        Write-ColorOutput " ✗ Failed to restore policy '$policyName': $($_.Exception.Message)" -Type Error
        Write-Verbose " Stack trace: $($_.ScriptStackTrace)"
        return $false
    }
}

function Restore-RuleInternal {
    <#
    .SYNOPSIS
        Internal function to restore a single rule.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object]$RuleData,
        
        [Parameter(Mandatory = $true)]
        [string]$PolicyName,
        
        [Parameter(Mandatory = $false)]
        [bool]$WhatIf = $false
    )
    
    $ruleName = $RuleData.RuleName
    
    try {
        if ($WhatIf) {
            Write-Verbose " [WHATIF] Would create rule: $ruleName"
            return $true
        }
        
        # Build rule parameters (basic set - extend as needed)
        $ruleParams = @{
            Name = $ruleName
            Policy = $PolicyName
        }
        
        # Add rule properties
        if ($RuleData.Disabled) { $ruleParams['Disabled'] = $RuleData.Disabled }
        if ($RuleData.Priority) { $ruleParams['Priority'] = $RuleData.Priority }
        if ($RuleData.Comment) { $ruleParams['Comment'] = $RuleData.Comment }
        
        # Sensitive information types (most critical property)
        if ($RuleData.ContentContainsSensitiveInformation) {
            try {
                $sitConfig = $RuleData.ContentContainsSensitiveInformation | ConvertFrom-Json -ErrorAction Stop
                $ruleParams['ContentContainsSensitiveInformation'] = $sitConfig
            }
            catch {
                Write-Verbose " Warning: Could not parse SIT configuration, skipping"
            }
        }
        
        # Advanced Rule (JSON-based conditions)
        if ($RuleData.AdvancedRule) {
            $ruleParams['AdvancedRule'] = $RuleData.AdvancedRule
        }
        
        # Notification settings
        if ($RuleData.NotifyUser) {
            $notifyArray = $RuleData.NotifyUser -split "; " | Where-Object { $_ -ne "" }
            if ($notifyArray.Count -gt 0) { $ruleParams['NotifyUser'] = $notifyArray }
        }
        if ($RuleData.NotifyPolicyTipCustomText) { $ruleParams['NotifyPolicyTipCustomText'] = $RuleData.NotifyPolicyTipCustomText }
        if ($RuleData.NotifyEmailCustomText) { $ruleParams['NotifyEmailCustomText'] = $RuleData.NotifyEmailCustomText }
        if ($RuleData.NotifyAllowOverride) {
            $overrideArray = $RuleData.NotifyAllowOverride -split "; " | Where-Object { $_ -ne "" }
            if ($overrideArray.Count -gt 0) { $ruleParams['NotifyAllowOverride'] = $overrideArray }
        }
        
        # Incident reporting
        if ($RuleData.GenerateIncidentReport) {
            $reportArray = $RuleData.GenerateIncidentReport -split "; " | Where-Object { $_ -ne "" }
            if ($reportArray.Count -gt 0) { $ruleParams['GenerateIncidentReport'] = $reportArray }
        }
        if ($RuleData.ReportSeverityLevel) { $ruleParams['ReportSeverityLevel'] = $RuleData.ReportSeverityLevel }
        
        # Actions
        if ($RuleData.BlockAccess) { $ruleParams['BlockAccess'] = $RuleData.BlockAccess }
        if ($RuleData.BlockAccessScope) { $ruleParams['BlockAccessScope'] = $RuleData.BlockAccessScope }
        if ($RuleData.EncryptRMSTemplate) { $ruleParams['EncryptRMSTemplate'] = $RuleData.EncryptRMSTemplate }
        
        # Create the rule
        $newRule = New-DlpComplianceRule @ruleParams -ErrorAction Stop
        Write-Verbose " ✓ Created rule: $ruleName"
        
        return $true
    }
    catch {
        Write-ColorOutput " ✗ Failed to restore rule '$ruleName': $($_.Exception.Message)" -Type Error
        Write-Verbose " Stack trace: $($_.ScriptStackTrace)"
        return $false
    }
}

function Add-PolicyMigrationDetailsInternal {
    <#
    .SYNOPSIS
        Internal function to add policy details to migration report.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object]$Report,
        
        [Parameter(Mandatory = $true)]
        [object]$PolicyData,
        
        [Parameter(Mandatory = $true)]
        [hashtable]$Changes
    )
    
    $policyReport = [PSCustomObject]@{
        PolicyName = $PolicyData.PolicyName
        PolicyId = $PolicyData.PolicyId
        HadSpecificTargeting = $PolicyData.HasSpecificTargeting
        HadLocationExceptions = $PolicyData.HasLocationExceptions
        WasCrossTenantPortable = $PolicyData.IsCrossTenantPortable
        Changes = @()
        Warnings = @()
    }
    
    # Document location changes
    foreach ($location in $Changes.Keys) {
        $change = $Changes[$location]
        $policyReport.Changes += [PSCustomObject]@{
            Location = $location
            OriginalValue = $change.Original
            NewValue = $change.New
            Reason = $change.Reason
            ImpactLevel = $change.ImpactLevel
        }
    }
    
    # Add warnings
    if ($PolicyData.HasSpecificTargeting) {
        $policyReport.Warnings += "Policy had specific user/group/site targeting that was converted to 'All'"
    }
    if ($PolicyData.HasLocationExceptions) {
        $policyReport.Warnings += "Policy had location exceptions that were removed (not supported in cross-tenant migration)"
    }
    
    $Report.PoliciesProcessed += $policyReport
    
    if ($policyReport.Changes.Count -gt 0 -or $policyReport.Warnings.Count -gt 0) {
        $Report.PoliciesWithChanges++
    }
}

function Export-MigrationReportInternal {
    <#
    .SYNOPSIS
        Internal function to export migration report as HTML.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object]$Report,
        
        [Parameter(Mandatory = $true)]
        [string]$OutputPath
    )
    
    # 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
    }
    
    # Build simple HTML report
    $html = @"
<!DOCTYPE html>
<html>
<head>
    <title>DLP Cross-Tenant Migration Report</title>
    <style>
        body { font-family: 'Segoe UI', Arial, sans-serif; margin: 20px; background: #f5f5f5; }
        .container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
        h1 { color: #0078d4; border-bottom: 3px solid #0078d4; padding-bottom: 10px; }
        h2 { color: #106ebe; margin-top: 30px; }
        .summary { background: #e7f3ff; padding: 15px; border-radius: 5px; margin: 20px 0; }
        .policy { border: 1px solid #ddd; margin: 15px 0; padding: 15px; border-radius: 5px; }
        .policy-name { font-size: 18px; font-weight: bold; color: #333; }
        .warning { background: #fff3cd; border-left: 4px solid #ffc107; padding: 10px; margin: 10px 0; }
        .change { background: #f8f9fa; border-left: 4px solid #17a2b8; padding: 10px; margin: 10px 0; }
        .footer { margin-top: 40px; padding-top: 20px; border-top: 2px solid #ddd; color: #666; font-size: 12px; text-align: center; }
    </style>
</head>
<body>
    <div class="container">
        <h1>🔄 DLP Cross-Tenant Migration Report</h1>
        
        <div class="summary">
            <h2>Migration Summary</h2>
            <p><strong>Migration Date:</strong> $($Report.MigrationDate)</p>
            <p><strong>Source Backup:</strong> $($Report.SourceBackupFile)</p>
            <p><strong>Total Policies:</strong> $($Report.TotalPolicies)</p>
            <p><strong>Policies with Changes:</strong> $($Report.PoliciesWithChanges)</p>
        </div>
        
        <h2>Policy Details</h2>
"@

    
    foreach ($policy in $Report.PoliciesProcessed) {
        $html += @"
        <div class="policy">
            <div class="policy-name">$($policy.PolicyName)</div>
            <p><strong>Original Portability:</strong> $(if ($policy.WasCrossTenantPortable) { "✅ Portable" } else { "⚠️ Not Portable" })</p>
"@

        
        if ($policy.Warnings.Count -gt 0) {
            foreach ($warning in $policy.Warnings) {
                $html += "<div class='warning'>⚠️ $warning</div>"
            }
        }
        
        if ($policy.Changes.Count -gt 0) {
            $html += "<h3>Location Changes:</h3>"
            foreach ($change in $policy.Changes) {
                $html += @"
            <div class='change'>
                <strong>$($change.Location):</strong> $($change.OriginalValue) → $($change.NewValue)<br/>
                <em>Reason: $($change.Reason)</em>
            </div>
"@

            }
        }
        
        $html += "</div>"
    }
    
    $html += @"
        
        <div class="footer">
            Generated by PurviewDLP Module | $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
        </div>
    </div>
</body>
</html>
"@

    
    # Save report
    $html | Out-File -FilePath $OutputPath -Encoding UTF8 -Force
}

#endregion