Powershell : Intune Backup and configuration Compare

This is a script that will not only backup your InTune configuration and compliance policies but tell you if changes have been made to those policy (that is a simple case of parsing the XML) the will use Graph with API permissions to back this data.

Then once your polices have been backed up, which will only include configuration and compliance polices then if you wish you can run the compare command or the deep drive compare command that will tell you what has changed.

Pre-Flight : Create the App Registration

First we need to create the App Registration so we need to head over to Entra:



Then we need App Registrations


Then we need a new registration as below:




Then we need to give it a valid name and ensure its single tenant then Register this application:



From here we need Certificates and Secrets as below:



Then ensure you have "client secrets" selected and choose "New client secret"



You then need to give that secret a name and lifetime:


You will then see this secret below:

Note : You will only see the value once when you navigate away from this screen and back the value will no longer be visible!



Next we need API permissions:


Then you need an API permission, for this we need Microsoft Graph:


Then you need an application permission:


Now we need to find the permissions we require which are:

DeviceManagementConfiguration.Read.All
DeviceManagementManagedDevices.Read.All

Enter one of those options into the search and choose that option then place a tick in that permission as below:



Add all the permissions with this method, then when you add all the permission you should see then as valid API permissions as below:


You then need to grant admin consent for these permissions:


Which you will need to confirm:


Then you will see that the "granted consent" is now approved with the green ticks:


You will also need to know the tenant ID and application ID which you can get from the overview section of the App Registration:



You will need the following information for this script to work so keep it handy:

Tenant ID
Application ID
Secret Key

Now we have the App Registration done we can now move on to the scripting to get the backups and do the compare operations.

Backup Configuration

First we need to backup the current configuration and compliance policies for managed devices which can be done with the script below, obviously remember to update the variables in bold before using it.

Script : intuneBackup.ps1 

# Import required modules
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.DeviceManagement

# App Registration Configuration
$tenantId = "<tenant_id>"
$clientId = "<application_id>"
$clientSecret = "<secret>"

# Backup Configuration
$BackupPath = "C:\Quarantine\intuneBackup\Backup"
$LogPath = "$BackupPath\Logs"

function Write-VerboseOutput {
    param(
        [string]$Message,
        [string]$Color = "White"
    )
    Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Message" -ForegroundColor $Color
    Add-Content -Path "$LogPath\backup_log.txt" -Value "$(Get-Date) - $Message"
}

function New-BackupFolder {
    param([string]$Path)
    if (!(Test-Path -Path $Path)) {
        Write-VerboseOutput "Creating directory: $Path" -Color Cyan
        New-Item -ItemType Directory -Path $Path -Force | Out-Null
    }
}

function Export-IntuneConfiguration {
    param (
        [string]$BackupRoot,
        [string]$ConfigType
    )
    
    $exportPath = Join-Path -Path $BackupRoot -ChildPath $ConfigType
    New-BackupFolder -Path $exportPath
    
    try {
        Write-VerboseOutput "Starting export of $ConfigType..." -Color Yellow
        
        switch ($ConfigType) {
            "DeviceConfigurations" {
                $configs = Get-MgDeviceManagementDeviceConfiguration -All
                foreach ($config in $configs) {
                    $fileName = "$($config.DisplayName -replace '[<>:"/\\|?*]', '_').xml"
                    $config | Export-Clixml -Path (Join-Path -Path $exportPath -ChildPath $fileName)
                    Write-VerboseOutput "Exported: $($config.DisplayName)" -Color White
                }
            }
            "CompliancePolicies" {
                $policies = Get-MgDeviceManagementDeviceCompliancePolicy -All
                foreach ($policy in $policies) {
                    $fileName = "$($policy.DisplayName -replace '[<>:"/\\|?*]', '_').xml"
                    $policy | Export-Clixml -Path (Join-Path -Path $exportPath -ChildPath $fileName)
                    Write-VerboseOutput "Exported: $($policy.DisplayName)" -Color White
                }
            }
        }
        Write-VerboseOutput "Successfully completed export of $ConfigType" -Color Green
    }
    catch {
        Write-VerboseOutput "Failed to export $ConfigType : $_" -Color Red
    }
}

# Main script execution
try {
    Write-VerboseOutput "=== Starting Intune Backup Process ===" -Color Magenta
    
    # Create Log folder
    New-BackupFolder -Path $LogPath
    
    # Connect to Microsoft Graph
    Write-VerboseOutput "Connecting to Microsoft Graph..." -Color Yellow
    
    # Convert client secret to secure string
    $clientSecureString = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force
    
    # Create credential object
    $clientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $clientId, $clientSecureString
    
    # Connect to Graph
    Connect-MgGraph -TenantId $tenantId -ClientSecretCredential $clientSecretCredential
    
    Write-VerboseOutput "Successfully connected to Graph API" -Color Green
    
    # Create backup folder
    $timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
    $backupRoot = Join-Path -Path $BackupPath -ChildPath "IntuneBackup_$timestamp"
    New-BackupFolder -Path $backupRoot
    
    # Define configuration types to backup
    $configTypes = @(
        "DeviceConfigurations",
        "CompliancePolicies"
    )
    
    # Export configurations
    foreach ($configType in $configTypes) {
        Export-IntuneConfiguration -BackupRoot $backupRoot -ConfigType $configType
    }
    
    Write-VerboseOutput "=== Backup Process Completed ===" -Color Magenta
    Write-VerboseOutput "Backup location: $backupRoot" -Color Green
}
catch {
    Write-VerboseOutput "Backup failed: $_" -Color Red
}
finally {
    if (Get-MgContext) {
        Disconnect-MgGraph
        Write-VerboseOutput "Disconnected from Microsoft Graph" -Color Green
    }
}

This will then produce a folder called "Backups" which will be dated with the time the backup was taken as you can see below:


You will then find inside each backup folder you will notice that you have two folders one for CompliancePolicies and one for DeviceConfigurations and inside those folders will be the XML files of your policies.

When the script is run it should look like this:


Once complete you will have a backup of these configuration files, however if you wish to know if new ones have been added or removed then read on.

Backup Compare

If you wish to know what has been added or removed then you can use this script, you need to define the backup folder then it will select the two most recent backups automatically on perform the comparison on those and produce a HTML output.

Script : BackupCompare.ps1

# Import required modules
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.DeviceManagement

function Get-RecursiveFiles {
    param (
        [string]$Path,
        [string]$ConfigType
    )
    
    if (Test-Path $Path) {
        $allFiles = @()
        $files = Get-ChildItem -Path $Path -Filter "*.xml" -Recurse
        
        foreach ($file in $files) {
            $relativePath = $file.FullName.Substring($Path.Length + 1)
            $allFiles += [PSCustomObject]@{
                FullName = $file.FullName
                RelativePath = $relativePath
                Name = $file.Name
                BaseName = $file.BaseName
                Content = Import-Clixml -Path $file.FullName
            }
        }
        return $allFiles
    }
    return @()
}

function Write-VerboseOutput {
    param(
        [string]$Message,
        [string]$Color = "White"
    )
    Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Message" -ForegroundColor $Color
}

function Compare-IntuneBackups {
    param (
        [Parameter(Mandatory = $true)]
        [string]$BackupPath = "C:\Quarantine\intuneBackup\Backup",
        [string]$ReportPath = "C:\Quarantine\intuneBackup\Reports"
    )

    Write-VerboseOutput "Starting comparison of Intune backups..." -Color Cyan

    $backupFolders = Get-ChildItem -Path $BackupPath -Directory | 
        Where-Object { $_.Name -match 'IntuneBackup_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}' } | 
        Sort-Object CreationTime -Descending | 
        Select-Object -First 2

    if ($backupFolders.Count -lt 2) {
        Write-VerboseOutput "Error: Need at least two backups to compare" -Color Red
        return
    }

    $newBackup = $backupFolders[0]
    $oldBackup = $backupFolders[1]
    $totalOverallChanges = 0

    $changes = @{
        DeviceConfigurations = @{
            Added = @()
            Removed = @()
            Modified = @()
        }
        CompliancePolicies = @{
            Added = @()
            Removed = @()
            Modified = @()
        }
    }

    foreach ($configType in @("DeviceConfigurations", "CompliancePolicies")) {
        Write-VerboseOutput "Processing $configType..." -Color Yellow

        $oldPath = Join-Path $oldBackup.FullName $configType
        $newPath = Join-Path $newBackup.FullName $configType

        $oldFiles = Get-RecursiveFiles -Path $oldPath -ConfigType $configType
        $newFiles = Get-RecursiveFiles -Path $newPath -ConfigType $configType

        $added = $newFiles | Where-Object { 
            $relativePath = $_.RelativePath
            -not ($oldFiles | Where-Object { $_.RelativePath -eq $relativePath })
        }
        
        foreach ($item in $added) {
            Write-VerboseOutput "  + Added: $($item.RelativePath)" -Color Green
            $changes[$configType].Added += [PSCustomObject]@{
                Name = $item.RelativePath.Replace(".xml", "")
                Type = "Added"
            }
        }

        $removed = $oldFiles | Where-Object { 
            $relativePath = $_.RelativePath
            -not ($newFiles | Where-Object { $_.RelativePath -eq $relativePath })
        }
        
        foreach ($item in $removed) {
            Write-VerboseOutput "  - Removed: $($item.RelativePath)" -Color Red
            $changes[$configType].Removed += [PSCustomObject]@{
                Name = $item.RelativePath.Replace(".xml", "")
                Type = "Removed"
            }
        }

        $existingFiles = $newFiles | Where-Object { 
            $relativePath = $_.RelativePath
            $oldFiles | Where-Object { $_.RelativePath -eq $relativePath }
        }

        foreach ($newFile in $existingFiles) {
            $oldFile = $oldFiles | Where-Object { $_.RelativePath -eq $newFile.RelativePath }
            Write-VerboseOutput "  Comparing: $($newFile.RelativePath)" -Color White
            
            $diff = Compare-Object -ReferenceObject ($oldFile.Content | ConvertTo-Json -Depth 20 -Compress) `
                                 -DifferenceObject ($newFile.Content | ConvertTo-Json -Depth 20 -Compress)
            
            if ($diff) {
                Write-VerboseOutput "  * Modified: $($newFile.RelativePath)" -Color Yellow
                $changes[$configType].Modified += [PSCustomObject]@{
                    Name = $newFile.RelativePath.Replace(".xml", "")
                    Type = "Modified"
                }
            }
        }

        $totalChanges = ($changes[$configType].Added + $changes[$configType].Removed + $changes[$configType].Modified).Count
        $totalOverallChanges += $totalChanges
        Write-VerboseOutput "Total changes in $configType : $totalChanges" -Color Cyan
    }

    # Generate HTML Report
    $reportDate = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
    $htmlFile = Join-Path $ReportPath "IntuneComparisonReport_$reportDate.html"

    $html = @"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Intune Configuration Changes Report</title>
    <style>
        :root {
            --primary-color: #0078d4;
            --success-color: #107c10;
            --warning-color: #ff8c00;
            --danger-color: #d13438;
            --background-color: #f9f9f9;
            --text-color: #323130;
            --border-color: #edebe9;
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            line-height: 1.6;
            color: var(--text-color);
            background-color: var(--background-color);
            padding: 2rem;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            padding: 2rem;
        }

        h1 {
            color: var(--primary-color);
            font-size: 1.8rem;
            margin-bottom: 1.5rem;
            padding-bottom: 1rem;
            border-bottom: 2px solid var(--border-color);
        }

        h2 {
            color: var(--text-color);
            font-size: 1.4rem;
            margin: 2rem 0 1rem;
        }

        .summary {
            background-color: white;
            padding: 1.5rem;
            border-radius: 6px;
            margin-bottom: 2rem;
            border: 1px solid var(--border-color);
        }

        .summary p {
            margin-bottom: 0.5rem;
        }

        .timestamp {
            color: #666;
            font-size: 0.9rem;
        }

        .section {
            margin: 2rem 0;
        }

        table {
            width: 100%;
            border-collapse: collapse;
            margin: 1rem 0;
            background: white;
            border-radius: 6px;
            overflow: hidden;
        }

        th {
            background-color: var(--primary-color);
            color: white;
            font-weight: 500;
            text-align: left;
            padding: 1rem;
        }

        td {
            padding: 0.8rem 1rem;
            border-bottom: 1px solid var(--border-color);
        }

        tr:last-child td {
            border-bottom: none;
        }

        .changes-badge {
            display: inline-block;
            padding: 0.25rem 0.75rem;
            border-radius: 12px;
            font-size: 0.8rem;
            font-weight: 500;
            margin-left: 1rem;
            background: var(--primary-color);
            color: white;
        }

        .change-type {
            display: inline-block;
            padding: 0.25rem 0.75rem;
            border-radius: 4px;
            font-size: 0.85rem;
            font-weight: 500;
        }

        .added { background-color: #e6f3e6; color: var(--success-color); }
        .removed { background-color: #fde7e9; color: var(--danger-color); }
        .modified { background-color: #fff4e5; color: var(--warning-color); }

        .no-changes {
            text-align: center;
            color: #666;
            padding: 2rem;
            font-style: italic;
        }

        .status-banner {
            background-color: #e6f3e6;
            color: var(--success-color);
            padding: 1rem;
            text-align: center;
            border-radius: 6px;
            margin: 1rem 0;
            font-weight: 500;
        }

        @media (max-width: 768px) {
            body { padding: 1rem; }
            .container { padding: 1rem; }
            table { display: block; overflow-x: auto; }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Intune Configuration Changes Report</h1>
        
        <div class="summary">
            <p><strong>Comparison Details</strong></p>
            <p>New Backup: <span class="highlight">$($newBackup.Name)</span></p>
            <p>Old Backup: <span class="highlight">$($oldBackup.Name)</span></p>
            <p class="timestamp">Generated: $(Get-Date)</p>
        </div>
"@

    if ($totalOverallChanges -eq 0) {
        $html += @"
        <div class="status-banner">
            No changes detected between backups
        </div>
"@
    }

    foreach ($configType in $changes.Keys) {
        $totalChanges = ($changes[$configType].Added + $changes[$configType].Removed + $changes[$configType].Modified).Count
        
        $html += @"
        <div class="section">
            <h2>$configType <span class="changes-badge">$totalChanges changes</span></h2>
            <table>
                <thead>
                    <tr>
                        <th>Configuration Name</th>
                        <th>Change Type</th>
                    </tr>
                </thead>
                <tbody>
"@

        if ($totalChanges -eq 0) {
            $html += @"
                    <tr>
                        <td colspan="2" class="no-changes">No changes detected in $configType</td>
                    </tr>
"@
        }
        else {
            foreach ($change in @($changes[$configType].Added + $changes[$configType].Removed + $changes[$configType].Modified) | Sort-Object Name) {
                $html += @"
                    <tr>
                        <td>$($change.Name)</td>
                        <td><span class="change-type $($change.Type.ToLower())">$($change.Type)</span></td>
                    </tr>
"@
            }
        }

        $html += @"
                </tbody>
            </table>
        </div>
"@
    }

    $html += @"
    </div>
</body>
</html>
"@

    if (!(Test-Path $ReportPath)) {
        New-Item -ItemType Directory -Path $ReportPath -Force | Out-Null
    }

    $html | Out-File -FilePath $htmlFile -Encoding UTF8
    Write-VerboseOutput "Report generated successfully: $htmlFile" -Color Green
    return $htmlFile
}

try {
    $result = Compare-IntuneBackups -BackupPath "C:\Quarantine\intuneBackup\Backup"
    if ($result) {
        Write-VerboseOutput "Comparison completed successfully" -Color Green
        Write-VerboseOutput "Report available at: $result" -Color Green
    }
} catch {
    Write-VerboseOutput "Error during comparison: $_" -Color Red
}

This will run and select the two most recent backups and then analyse all the data in those backups as below, which will end with a summary as below:


If you then look for the "Report" directory you can then view all the reports as below:

This will then produce a report like this, in this case no changes were made:


What has changed "within" the policy?

If you are looking to what specially has been changed within the policy then you can run the "deep dive" script that will do this analysis on each configuration item 

Script : DeepDiveCompare.ps1

# Import required modules
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.DeviceManagement

function Get-RecursiveFiles {
    param (
        [string]$Path,
        [string]$ConfigType
    )
    
    if (Test-Path $Path) {
        $allFiles = @()
        $files = Get-ChildItem -Path $Path -Filter "*.xml" -Recurse
        
        foreach ($file in $files) {
            $relativePath = $file.FullName.Substring($Path.Length + 1)
            $allFiles += [PSCustomObject]@{
                FullName = $file.FullName
                RelativePath = $relativePath
                Name = $file.Name
                BaseName = $file.BaseName
                Content = Import-Clixml -Path $file.FullName
            }
        }
        return $allFiles
    }
    return @()
}

function Compare-PolicyContent {
    param (
        $OldContent,
        $NewContent
    )
    
    $changes = @()
    
    # Properties to ignore
    $ignoreProperties = @(
        'ConfigurationVersion',
        'ErrorCount',
        'FailedCount',
        'Id',
        'LastUpdateDateTime',
        'NotApplicableCount',
        'PendingCount',
        'SuccessCount',
        'AdditionalProperties',
        'DeviceStatusOverview'
    )
    
    $oldJson = $OldContent | ConvertTo-Json -Depth 20
    $newJson = $NewContent | ConvertTo-Json -Depth 20
    
    $oldObj = $oldJson | ConvertFrom-Json
    $newObj = $newJson | ConvertFrom-Json
    
    $allProperties = ($oldObj.PSObject.Properties.Name + $newObj.PSObject.Properties.Name) | 
        Select-Object -Unique | 
        Where-Object { $_ -notin $ignoreProperties }
    
    foreach ($prop in $allProperties) {
        $oldValue = $oldObj.$prop
        $newValue = $newObj.$prop

        # Convert to JSON for proper comparison
        $oldJsonValue = $oldValue | ConvertTo-Json -Compress
        $newJsonValue = $newValue | ConvertTo-Json -Compress
        
        if (($null -ne $oldValue -or $null -ne $newValue) -and 
            ($oldJsonValue -ne $newJsonValue) -and 
            -not ($oldJsonValue -eq "{}" -and $newJsonValue -eq "{}")) {
            
            $changes += [PSCustomObject]@{
                Property = $prop
                OldValue = $oldValue
                NewValue = $newValue
            }
        }
    }
    
    return $changes
}

function Write-VerboseOutput {
    param(
        [string]$Message,
        [string]$Color = "White"
    )
    Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Message" -ForegroundColor $Color
}

function Compare-IntuneBackups {
    param (
        [Parameter(Mandatory = $true)]
        [string]$BackupPath = "C:\Quarantine\intuneBackup\Backup",
        [string]$ReportPath = "C:\Quarantine\intuneBackup\Reports"
    )

    Write-VerboseOutput "Starting comparison of Intune backups..." -Color Cyan

    $backupFolders = Get-ChildItem -Path $BackupPath -Directory | 
        Where-Object { $_.Name -match 'IntuneBackup_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}' } | 
        Sort-Object CreationTime -Descending | 
        Select-Object -First 2

    if ($backupFolders.Count -lt 2) {
        Write-VerboseOutput "Error: Need at least two backups to compare" -Color Red
        return
    }

    $newBackup = $backupFolders[0]
    $oldBackup = $backupFolders[1]
    $totalOverallChanges = 0

    $changes = @{
        DeviceConfigurations = @{
            Added = @()
            Removed = @()
            Modified = @()
        }
        CompliancePolicies = @{
            Added = @()
            Removed = @()
            Modified = @()
        }
    }

    foreach ($configType in @("DeviceConfigurations", "CompliancePolicies")) {
        Write-VerboseOutput "Processing $configType..." -Color Yellow

        $oldPath = Join-Path $oldBackup.FullName $configType
        $newPath = Join-Path $newBackup.FullName $configType

        $oldFiles = Get-RecursiveFiles -Path $oldPath -ConfigType $configType
        $newFiles = Get-RecursiveFiles -Path $newPath -ConfigType $configType

        $added = $newFiles | Where-Object { 
            $relativePath = $_.RelativePath
            -not ($oldFiles | Where-Object { $_.RelativePath -eq $relativePath })
        }
        
        foreach ($item in $added) {
            Write-VerboseOutput "  + Added: $($item.RelativePath)" -Color Green
            $changes[$configType].Added += [PSCustomObject]@{
                Name = $item.RelativePath.Replace(".xml", "")
                Type = "Added"
                Content = $item.Content
            }
        }

        $removed = $oldFiles | Where-Object { 
            $relativePath = $_.RelativePath
            -not ($newFiles | Where-Object { $_.RelativePath -eq $relativePath })
        }
        
        foreach ($item in $removed) {
            Write-VerboseOutput "  - Removed: $($item.RelativePath)" -Color Red
            $changes[$configType].Removed += [PSCustomObject]@{
                Name = $item.RelativePath.Replace(".xml", "")
                Type = "Removed"
                Content = $item.Content
            }
        }

        $existingFiles = $newFiles | Where-Object { 
            $relativePath = $_.RelativePath
            $oldFiles | Where-Object { $_.RelativePath -eq $relativePath }
        }

        foreach ($newFile in $existingFiles) {
            $oldFile = $oldFiles | Where-Object { $_.RelativePath -eq $newFile.RelativePath }
            Write-VerboseOutput "  Comparing: $($newFile.RelativePath)" -Color White
            
            $policyChanges = Compare-PolicyContent -OldContent $oldFile.Content -NewContent $newFile.Content
            
            if ($policyChanges) {
                Write-VerboseOutput "  * Modified: $($newFile.RelativePath)" -Color Yellow
                $changes[$configType].Modified += [PSCustomObject]@{
                    Name = $newFile.RelativePath.Replace(".xml", "")
                    Type = "Modified"
                    Changes = $policyChanges
                }
            }
        }

        $totalChanges = ($changes[$configType].Added + $changes[$configType].Removed + $changes[$configType].Modified).Count
        $totalOverallChanges += $totalChanges
        Write-VerboseOutput "Total changes in $configType : $totalChanges" -Color Cyan
    }

    # Generate HTML Report
    $reportDate = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
    $htmlFile = Join-Path $ReportPath "DeepDive_$reportDate.html"

    $html = @"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Intune Configuration Changes Report</title>
    <style>
        :root {
            --primary-color: #0078d4;
            --success-color: #107c10;
            --warning-color: #ff8c00;
            --danger-color: #d13438;
            --background-color: #f9f9f9;
            --text-color: #323130;
            --border-color: #edebe9;
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            line-height: 1.6;
            color: var(--text-color);
            background-color: var(--background-color);
            padding: 2rem;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            padding: 2rem;
        }

        h1, h2, h3 {
            color: var(--primary-color);
            margin-bottom: 1rem;
        }

        .summary {
            background-color: white;
            padding: 1.5rem;
            border-radius: 6px;
            margin-bottom: 2rem;
            border: 1px solid var(--border-color);
        }

        .no-changes {
            text-align: center;
            padding: 20px;
            color: #666;
            font-style: italic;
            background-color: #f8f9fa;
            border-radius: 4px;
            margin: 1rem 0;
        }

        .changes-detail {
            margin: 1rem 0;
            padding: 1rem;
            background-color: #f8f9fa;
            border-radius: 4px;
        }

        .change-property {
            margin-bottom: 0.5rem;
            padding: 0.5rem;
            background-color: white;
            border-left: 3px solid var(--primary-color);
        }

        table {
            width: 100%;
            border-collapse: collapse;
            margin: 1rem 0;
            background: white;
        }

        th, td {
            padding: 0.8rem;
            border: 1px solid var(--border-color);
            text-align: left;
        }

        th {
            background-color: var(--primary-color);
            color: white;
        }

        .change-type {
            display: inline-block;
            padding: 0.25rem 0.75rem;
            border-radius: 4px;
            font-weight: 500;
        }

        .added { background-color: #e6f3e6; color: var(--success-color); }
        .removed { background-color: #fde7e9; color: var(--danger-color); }
        .modified { background-color: #fff4e5; color: var(--warning-color); }

        .policy-details {
            margin-top: 1rem;
            padding: 1rem;
            background-color: #f8f9fa;
            border-radius: 4px;
        }

        .value-change {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 1rem;
            margin-top: 0.5rem;
        }

        .old-value, .new-value {
            padding: 0.5rem;
            background-color: white;
            border-radius: 4px;
        }

        .old-value { border-left: 3px solid var(--danger-color); }
        .new-value { border-left: 3px solid var(--success-color); }

        pre {
            white-space: pre-wrap;
            word-wrap: break-word;
        }

        @media (max-width: 768px) {
            body { padding: 1rem; }
            .container { padding: 1rem; }
            .value-change { grid-template-columns: 1fr; }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Intune Configuration Changes Report</h1>
        
        <div class="summary">
            <p><strong>Comparison Details</strong></p>
            <p>New Backup: $($newBackup.Name)</p>
            <p>Old Backup: $($oldBackup.Name)</p>
            <p>Generated: $(Get-Date)</p>
        </div>
"@

    if ($totalOverallChanges -eq 0) {
        $html += @"
        <div class="no-changes">
            No changes detected in any configuration policies
        </div>
"@
    }

    foreach ($configType in $changes.Keys) {
        $totalTypeChanges = ($changes[$configType].Added.Count + 
                            $changes[$configType].Removed.Count + 
                            $changes[$configType].Modified.Count)
        
        $html += "<h2>$configType Changes</h2>"
        
        if ($totalTypeChanges -eq 0) {
            $html += @"
            <div class="no-changes">
                No changes detected in $configType
            </div>
"@
            continue
        }
        
        # Added Policies
        if ($changes[$configType].Added.Count -gt 0) {
            $html += @"
            <h3>Added Policies</h3>
            <table>
                <tr>
                    <th>Policy Name</th>
                    <th>Configuration</th>
                </tr>
"@
            foreach ($policy in $changes[$configType].Added) {
                $configDetails = $policy.Content | ConvertTo-Json -Depth 10
                $html += @"
                <tr>
                    <td><span class="change-type added">Added</span> $($policy.Name)</td>
                    <td>
                        <div class="policy-details">
                            <pre>$configDetails</pre>
                        </div>
                    </td>
                </tr>
"@
            }
            $html += "</table>"
        }

        # Removed Policies
        if ($changes[$configType].Removed.Count -gt 0) {
            $html += @"
            <h3>Removed Policies</h3>
            <table>
                <tr>
                    <th>Policy Name</th>
                    <th>Previous Configuration</th>
                </tr>
"@
            foreach ($policy in $changes[$configType].Removed) {
                $configDetails = $policy.Content | ConvertTo-Json -Depth 10
                $html += @"
                <tr>
                    <td><span class="change-type removed">Removed</span> $($policy.Name)</td>
                    <td>
                        <div class="policy-details">
                            <pre>$configDetails</pre>
                        </div>
                    </td>
                </tr>
"@
            }
            $html += "</table>"
        }

        # Modified Policies
        if ($changes[$configType].Modified.Count -gt 0) {
            $html += @"
            <h3>Modified Policies</h3>
            <table>
                <tr>
                    <th>Policy Name</th>
                    <th>Changes</th>
                </tr>
"@
            foreach ($policy in $changes[$configType].Modified) {
                $html += @"
                <tr>
                    <td><span class="change-type modified">Modified</span> $($policy.Name)</td>
                    <td>
                        <div class="changes-detail">
"@
                foreach ($change in $policy.Changes) {
                    $oldValue = $change.OldValue | ConvertTo-Json -Depth 5
                    $newValue
$newValue = $change.NewValue | ConvertTo-Json -Depth 5
                    $html += @"
                                <div class="change-property">
                                    <strong>$($change.Property)</strong>
                                    <div class="value-change">
                                        <div class="old-value">
                                            <strong>Old Value:</strong><br>
                                            <pre>$oldValue</pre>
                                        </div>
                                        <div class="new-value">
                                            <strong>New Value:</strong><br>
                                            <pre>$newValue</pre>
                                        </div>
                                    </div>
                                </div>
"@
                }
                $html += @"
                        </div>
                    </td>
                </tr>
"@
            }
            $html += "</table>"
        }
    }

    $html += @"
    </div>
</body>
</html>
"@

    if (!(Test-Path $ReportPath)) {
        New-Item -ItemType Directory -Path $ReportPath -Force | Out-Null
    }

    $html | Out-File -FilePath $htmlFile -Encoding UTF8
    Write-VerboseOutput "Report generated successfully: $htmlFile" -Color Green
    return $htmlFile
}

try {
    $result = Compare-IntuneBackups -BackupPath "C:\Quarantine\intuneBackup\Backup"
    if ($result) {
        Write-VerboseOutput "Comparison completed successfully" -Color Green
        Write-VerboseOutput "Report available at: $result" -Color Green
    }
} catch {
    Write-VerboseOutput "Error during comparison: $_" -Color Red
}

That script will create a report called "DeepDive" as you can see below, its the top file below:


When you view the graphical report, it will break down the changes, if in this example there were any and show you then in the report:


Previous Post Next Post

نموذج الاتصال