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 App Registrations
Then we need a new registration as below:
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:
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:
Enter one of those options into the search and choose that option then place a tick in that permission as below:
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:
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:
Tenant ID
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:
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.
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
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?
# 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: