1
1
function Compare-CIPPIntuneObject {
2
- <#
3
- . SYNOPSIS
4
- Compares two Intune objects and returns only the differences.
5
-
6
- . DESCRIPTION
7
- This function takes two Intune objects and performs a comparison, returning only the properties that differ.
8
- If no differences are found, it returns null.
9
- It's useful for identifying changes between template objects and existing policies.
10
-
11
- . PARAMETER ReferenceObject
12
- The reference Intune object to compare against.
13
-
14
- . PARAMETER DifferenceObject
15
- The Intune object to compare with the reference object.
16
-
17
- . PARAMETER ExcludeProperties
18
- Additional properties to exclude from the comparison.
19
-
20
- . EXAMPLE
21
- $template = Get-CIPPIntunePolicy -tenantFilter $Tenant -DisplayName "Template Policy" -TemplateType "Device"
22
- $existing = Get-CIPPIntunePolicy -tenantFilter $Tenant -DisplayName "Existing Policy" -TemplateType "Device"
23
- $differences = Compare-CIPPIntuneObject -ReferenceObject $template -DifferenceObject $existing
24
-
25
- . NOTES
26
- This function performs a comparison of objects, including nested properties.
27
- #>
28
2
[CmdletBinding ()]
29
3
param (
30
4
[Parameter (Mandatory = $true )]
@@ -39,7 +13,6 @@ function Compare-CIPPIntuneObject {
39
13
[string []]$CompareType = @ ()
40
14
)
41
15
if ($CompareType -ne ' Catalog' ) {
42
- # Default properties to exclude from comparison
43
16
$defaultExcludeProperties = @ (
44
17
' id' ,
45
18
' createdDateTime' ,
@@ -57,13 +30,9 @@ function Compare-CIPPIntuneObject {
57
30
' featureUpdatesPauseStartDate'
58
31
)
59
32
60
- # Combine default and custom exclude properties
61
33
$excludeProps = $defaultExcludeProperties + $ExcludeProperties
62
-
63
- # Create a list to store comparison results
64
34
$result = [System.Collections.Generic.List [PSObject ]]::new()
65
35
66
- # Helper function to check if a property should be skipped
67
36
function ShouldSkipProperty {
68
37
param (
69
38
[string ]$PropertyName
@@ -73,7 +42,6 @@ function Compare-CIPPIntuneObject {
73
42
$excludeProps -contains $PropertyName )
74
43
}
75
44
76
- # Recursive function to compare objects deeply
77
45
function Compare-ObjectsRecursively {
78
46
param (
79
47
[Parameter (Mandatory = $true )]
@@ -83,15 +51,24 @@ function Compare-CIPPIntuneObject {
83
51
$Object2 ,
84
52
85
53
[Parameter (Mandatory = $false )]
86
- [string ]$PropertyPath = ' '
54
+ [string ]$PropertyPath = ' ' ,
55
+ [int ]$Depth = 0 ,
56
+ [int ]$MaxDepth = 20
87
57
)
88
58
89
- # If both objects are null or empty, they're equal
59
+ if ($Depth -ge $MaxDepth ) {
60
+ $result.Add ([PSCustomObject ]@ {
61
+ Property = $PropertyPath
62
+ ExpectedValue = ' [MaxDepthExceeded]'
63
+ ReceivedValue = ' [MaxDepthExceeded]'
64
+ })
65
+ return
66
+ }
67
+
90
68
if (($null -eq $Object1 -or $Object1 -eq ' ' ) -and ($null -eq $Object2 -or $Object2 -eq ' ' )) {
91
69
return
92
70
}
93
71
94
- # If one object is null but the other isn't, they're different
95
72
if (($null -eq $Object1 -or $Object1 -eq ' ' ) -xor ($null -eq $Object2 -or $Object2 -eq ' ' )) {
96
73
$result.Add ([PSCustomObject ]@ {
97
74
Property = $PropertyPath
@@ -101,7 +78,6 @@ function Compare-CIPPIntuneObject {
101
78
return
102
79
}
103
80
104
- # If objects are of different types, they're different
105
81
if ($Object1.GetType () -ne $Object2.GetType ()) {
106
82
$result.Add ([PSCustomObject ]@ {
107
83
Property = $PropertyPath
@@ -111,9 +87,22 @@ function Compare-CIPPIntuneObject {
111
87
return
112
88
}
113
89
114
- # Handle different object types
90
+ # Short-circuit recursion for primitive types
91
+ $primitiveTypes = @ ([double ], [decimal ], [datetime ], [timespan ], [guid ] )
92
+ foreach ($type in $primitiveTypes ) {
93
+ if ($Object1 -is $type -and $Object2 -is $type ) {
94
+ if ($Object1 -ne $Object2 ) {
95
+ $result.Add ([PSCustomObject ]@ {
96
+ Property = $PropertyPath
97
+ ExpectedValue = $Object1
98
+ ReceivedValue = $Object2
99
+ })
100
+ }
101
+ return
102
+ }
103
+ }
104
+
115
105
if ($Object1 -is [System.Collections.IDictionary ]) {
116
- # Compare dictionaries
117
106
$allKeys = @ ($Object1.Keys ) + @ ($Object2.Keys ) | Select-Object - Unique
118
107
119
108
foreach ($key in $allKeys ) {
@@ -122,9 +111,8 @@ function Compare-CIPPIntuneObject {
122
111
$newPath = if ($PropertyPath ) { " $PropertyPath .$key " } else { $key }
123
112
124
113
if ($Object1.ContainsKey ($key ) -and $Object2.ContainsKey ($key )) {
125
- # only run if both props are not null
126
114
if ($Object1 [$key ] -and $Object2 [$key ]) {
127
- Compare-ObjectsRecursively - Object1 $Object1 [$key ] - Object2 $Object2 [$key ] - PropertyPath $newPath
115
+ Compare-ObjectsRecursively - Object1 $Object1 [$key ] - Object2 $Object2 [$key ] - PropertyPath $newPath - Depth ( $Depth + 1 ) - MaxDepth $MaxDepth
128
116
}
129
117
} elseif ($Object1.ContainsKey ($key )) {
130
118
$result.Add ([PSCustomObject ]@ {
@@ -141,14 +129,13 @@ function Compare-CIPPIntuneObject {
141
129
}
142
130
}
143
131
} elseif ($Object1 -is [Array ] -or $Object1 -is [System.Collections.IList ]) {
144
- # Compare arrays
145
132
$maxLength = [Math ]::Max($Object1.Count , $Object2.Count )
146
133
147
134
for ($i = 0 ; $i -lt $maxLength ; $i ++ ) {
148
135
$newPath = " $PropertyPath .$i "
149
136
150
137
if ($i -lt $Object1.Count -and $i -lt $Object2.Count ) {
151
- Compare-ObjectsRecursively - Object1 $Object1 [$i ] - Object2 $Object2 [$i ] - PropertyPath $newPath
138
+ Compare-ObjectsRecursively - Object1 $Object1 [$i ] - Object2 $Object2 [$i ] - PropertyPath $newPath - Depth ( $Depth + 1 ) - MaxDepth $MaxDepth
152
139
} elseif ($i -lt $Object1.Count ) {
153
140
$result.Add ([PSCustomObject ]@ {
154
141
Property = $newPath
@@ -164,7 +151,6 @@ function Compare-CIPPIntuneObject {
164
151
}
165
152
}
166
153
} elseif ($Object1 -is [PSCustomObject ] -or $Object1.PSObject.Properties.Count -gt 0 ) {
167
- # Compare PSCustomObjects or objects with properties
168
154
$allPropertyNames = @ (
169
155
$Object1.PSObject.Properties | Select-Object - ExpandProperty Name
170
156
$Object2.PSObject.Properties | Select-Object - ExpandProperty Name
@@ -178,9 +164,8 @@ function Compare-CIPPIntuneObject {
178
164
$prop2Exists = $Object2.PSObject.Properties.Name -contains $propName
179
165
180
166
if ($prop1Exists -and $prop2Exists ) {
181
- # only run if both props are not null
182
167
if ($Object1 .$propName -and $Object2 .$propName ) {
183
- Compare-ObjectsRecursively - Object1 $Object1 .$propName - Object2 $Object2 .$propName - PropertyPath $newPath
168
+ Compare-ObjectsRecursively - Object1 $Object1 .$propName - Object2 $Object2 .$propName - PropertyPath $newPath - Depth ( $Depth + 1 ) - MaxDepth $MaxDepth
184
169
}
185
170
} elseif ($prop1Exists ) {
186
171
$result.Add ([PSCustomObject ]@ {
@@ -197,7 +182,6 @@ function Compare-CIPPIntuneObject {
197
182
}
198
183
}
199
184
} else {
200
- # Compare primitive values
201
185
$val1 = $Object1.ToString ()
202
186
$val2 = $Object2.ToString ()
203
187
@@ -211,7 +195,6 @@ function Compare-CIPPIntuneObject {
211
195
}
212
196
}
213
197
214
- # Convert objects to PowerShell objects if they're not already
215
198
$obj1 = if ($ReferenceObject -is [string ]) {
216
199
$ReferenceObject | ConvertFrom-Json - AsHashtable - Depth 100
217
200
} else {
@@ -224,13 +207,10 @@ function Compare-CIPPIntuneObject {
224
207
$DifferenceObject
225
208
}
226
209
227
- # Start the recursive comparison
228
- # only do the compare if the objects are not null
229
210
if ($obj1 -and $obj2 ) {
230
211
Compare-ObjectsRecursively - Object1 $obj1 - Object2 $obj2
231
212
}
232
213
233
- # If no differences found, return null
234
214
if ($result.Count -eq 0 ) {
235
215
return $null
236
216
}
@@ -425,17 +405,14 @@ function Compare-CIPPIntuneObject {
425
405
$tempOutput
426
406
}
427
407
428
- # Compare the items and create result
429
408
$result = [System.Collections.Generic.List [PSObject ]]::new()
430
409
431
- # Group all items by Key for comparison
432
410
$allKeys = @ ($referenceItems | Select-Object - ExpandProperty Key) + @ ($differenceItems | Select-Object - ExpandProperty Key) | Sort-Object - Unique
433
411
434
412
foreach ($key in $allKeys ) {
435
413
$refItem = $referenceItems | Where-Object { $_.Key -eq $key } | Select-Object - First 1
436
414
$diffItem = $differenceItems | Where-Object { $_.Key -eq $key } | Select-Object - First 1
437
415
438
- # Get the setting definition ID from the key
439
416
$settingId = $key
440
417
if ($key -like ' Simple-*' ) {
441
418
$settingId = $key.Substring (7 )
@@ -447,28 +424,22 @@ function Compare-CIPPIntuneObject {
447
424
$settingId = $key.Substring (8 )
448
425
}
449
426
450
- # Look up the setting in the collection
451
427
$settingDefinition = $intuneCollection | Where-Object { $_.id -eq $settingId }
452
428
453
- # Get the raw values
454
429
$refRawValue = if ($refItem ) { $refItem.Value } else { $null }
455
430
$diffRawValue = if ($diffItem ) { $diffItem.Value } else { $null }
456
431
457
- # Try to translate the values to display names if they're option IDs
458
432
$refValue = $refRawValue
459
433
$diffValue = $diffRawValue
460
434
461
- # If the setting has options, try to find the display name for the values
462
435
if ($null -ne $settingDefinition -and $null -ne $settingDefinition.options ) {
463
- # For reference value
464
436
if ($null -ne $refRawValue -and $refRawValue -match ' _\d+$' ) {
465
437
$option = $settingDefinition.options | Where-Object { $_.id -eq $refRawValue }
466
438
if ($null -ne $option -and $null -ne $option.displayName ) {
467
439
$refValue = $option.displayName
468
440
}
469
441
}
470
442
471
- # For difference value
472
443
if ($null -ne $diffRawValue -and $diffRawValue -match ' _\d+$' ) {
473
444
$option = $settingDefinition.options | Where-Object { $_.id -eq $diffRawValue }
474
445
if ($null -ne $option -and $null -ne $option.displayName ) {
@@ -477,7 +448,6 @@ function Compare-CIPPIntuneObject {
477
448
}
478
449
}
479
450
480
- # Use the display name for the property label if available
481
451
$label = if ($null -ne $settingDefinition -and $null -ne $settingDefinition.displayName ) {
482
452
$settingDefinition.displayName
483
453
} elseif ($refItem ) {
@@ -488,7 +458,6 @@ function Compare-CIPPIntuneObject {
488
458
$key
489
459
}
490
460
491
- # Only add to result if values are different or one is missing
492
461
if ($refRawValue -ne $diffRawValue -or $null -eq $refRawValue -or $null -eq $diffRawValue ) {
493
462
$result.Add ([PSCustomObject ]@ {
494
463
Property = $label
@@ -502,4 +471,3 @@ function Compare-CIPPIntuneObject {
502
471
}
503
472
return $result
504
473
}
505
-
0 commit comments