11function 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- #>
282 [CmdletBinding ()]
293 param (
304 [Parameter (Mandatory = $true )]
@@ -39,7 +13,6 @@ function Compare-CIPPIntuneObject {
3913 [string []]$CompareType = @ ()
4014 )
4115 if ($CompareType -ne ' Catalog' ) {
42- # Default properties to exclude from comparison
4316 $defaultExcludeProperties = @ (
4417 ' id' ,
4518 ' createdDateTime' ,
@@ -57,13 +30,9 @@ function Compare-CIPPIntuneObject {
5730 ' featureUpdatesPauseStartDate'
5831 )
5932
60- # Combine default and custom exclude properties
6133 $excludeProps = $defaultExcludeProperties + $ExcludeProperties
62-
63- # Create a list to store comparison results
6434 $result = [System.Collections.Generic.List [PSObject ]]::new()
6535
66- # Helper function to check if a property should be skipped
6736 function ShouldSkipProperty {
6837 param (
6938 [string ]$PropertyName
@@ -73,25 +42,28 @@ function Compare-CIPPIntuneObject {
7342 $excludeProps -contains $PropertyName )
7443 }
7544
76- # Recursive function to compare objects deeply
7745 function Compare-ObjectsRecursively {
7846 param (
79- [Parameter (Mandatory = $true )]
80- $Object1 ,
81-
82- [Parameter (Mandatory = $true )]
83- $Object2 ,
84-
85- [Parameter (Mandatory = $false )]
86- [string ]$PropertyPath = ' '
47+ [Parameter (Mandatory = $true )] $Object1 ,
48+ [Parameter (Mandatory = $true )] $Object2 ,
49+ [Parameter (Mandatory = $false )] [string ]$PropertyPath = ' ' ,
50+ [int ]$Depth = 0 ,
51+ [int ]$MaxDepth = 15
8752 )
8853
89- # If both objects are null or empty, they're equal
54+ if ($Depth -ge $MaxDepth ) {
55+ $result.Add ([PSCustomObject ]@ {
56+ Property = $PropertyPath
57+ ExpectedValue = ' [MaxDepthExceeded]'
58+ ReceivedValue = ' [MaxDepthExceeded]'
59+ })
60+ return
61+ }
62+
9063 if (($null -eq $Object1 -or $Object1 -eq ' ' ) -and ($null -eq $Object2 -or $Object2 -eq ' ' )) {
9164 return
9265 }
9366
94- # If one object is null but the other isn't, they're different
9567 if (($null -eq $Object1 -or $Object1 -eq ' ' ) -xor ($null -eq $Object2 -or $Object2 -eq ' ' )) {
9668 $result.Add ([PSCustomObject ]@ {
9769 Property = $PropertyPath
@@ -101,7 +73,6 @@ function Compare-CIPPIntuneObject {
10173 return
10274 }
10375
104- # If objects are of different types, they're different
10576 if ($Object1.GetType () -ne $Object2.GetType ()) {
10677 $result.Add ([PSCustomObject ]@ {
10778 Property = $PropertyPath
@@ -111,9 +82,7 @@ function Compare-CIPPIntuneObject {
11182 return
11283 }
11384
114- # Handle different object types
11585 if ($Object1 -is [System.Collections.IDictionary ]) {
116- # Compare dictionaries
11786 $allKeys = @ ($Object1.Keys ) + @ ($Object2.Keys ) | Select-Object - Unique
11887
11988 foreach ($key in $allKeys ) {
@@ -122,9 +91,8 @@ function Compare-CIPPIntuneObject {
12291 $newPath = if ($PropertyPath ) { " $PropertyPath .$key " } else { $key }
12392
12493 if ($Object1.ContainsKey ($key ) -and $Object2.ContainsKey ($key )) {
125- # only run if both props are not null
12694 if ($Object1 [$key ] -and $Object2 [$key ]) {
127- Compare-ObjectsRecursively - Object1 $Object1 [$key ] - Object2 $Object2 [$key ] - PropertyPath $newPath
95+ Compare-ObjectsRecursively - Object1 $Object1 [$key ] - Object2 $Object2 [$key ] - PropertyPath $newPath - Depth ( $Depth + 1 ) - MaxDepth $MaxDepth
12896 }
12997 } elseif ($Object1.ContainsKey ($key )) {
13098 $result.Add ([PSCustomObject ]@ {
@@ -141,14 +109,13 @@ function Compare-CIPPIntuneObject {
141109 }
142110 }
143111 } elseif ($Object1 -is [Array ] -or $Object1 -is [System.Collections.IList ]) {
144- # Compare arrays
145112 $maxLength = [Math ]::Max($Object1.Count , $Object2.Count )
146113
147114 for ($i = 0 ; $i -lt $maxLength ; $i ++ ) {
148115 $newPath = " $PropertyPath .$i "
149116
150117 if ($i -lt $Object1.Count -and $i -lt $Object2.Count ) {
151- Compare-ObjectsRecursively - Object1 $Object1 [$i ] - Object2 $Object2 [$i ] - PropertyPath $newPath
118+ Compare-ObjectsRecursively - Object1 $Object1 [$i ] - Object2 $Object2 [$i ] - PropertyPath $newPath - Depth ( $Depth + 1 ) - MaxDepth $MaxDepth
152119 } elseif ($i -lt $Object1.Count ) {
153120 $result.Add ([PSCustomObject ]@ {
154121 Property = $newPath
@@ -164,7 +131,6 @@ function Compare-CIPPIntuneObject {
164131 }
165132 }
166133 } elseif ($Object1 -is [PSCustomObject ] -or $Object1.PSObject.Properties.Count -gt 0 ) {
167- # Compare PSCustomObjects or objects with properties
168134 $allPropertyNames = @ (
169135 $Object1.PSObject.Properties | Select-Object - ExpandProperty Name
170136 $Object2.PSObject.Properties | Select-Object - ExpandProperty Name
@@ -178,9 +144,8 @@ function Compare-CIPPIntuneObject {
178144 $prop2Exists = $Object2.PSObject.Properties.Name -contains $propName
179145
180146 if ($prop1Exists -and $prop2Exists ) {
181- # only run if both props are not null
182147 if ($Object1 .$propName -and $Object2 .$propName ) {
183- Compare-ObjectsRecursively - Object1 $Object1 .$propName - Object2 $Object2 .$propName - PropertyPath $newPath
148+ Compare-ObjectsRecursively - Object1 $Object1 .$propName - Object2 $Object2 .$propName - PropertyPath $newPath - Depth ( $Depth + 1 ) - MaxDepth $MaxDepth
184149 }
185150 } elseif ($prop1Exists ) {
186151 $result.Add ([PSCustomObject ]@ {
@@ -197,7 +162,6 @@ function Compare-CIPPIntuneObject {
197162 }
198163 }
199164 } else {
200- # Compare primitive values
201165 $val1 = $Object1.ToString ()
202166 $val2 = $Object2.ToString ()
203167
@@ -211,7 +175,6 @@ function Compare-CIPPIntuneObject {
211175 }
212176 }
213177
214- # Convert objects to PowerShell objects if they're not already
215178 $obj1 = if ($ReferenceObject -is [string ]) {
216179 $ReferenceObject | ConvertFrom-Json - AsHashtable - Depth 100
217180 } else {
@@ -224,20 +187,16 @@ function Compare-CIPPIntuneObject {
224187 $DifferenceObject
225188 }
226189
227- # Start the recursive comparison
228- # only do the compare if the objects are not null
229190 if ($obj1 -and $obj2 ) {
230191 Compare-ObjectsRecursively - Object1 $obj1 - Object2 $obj2
231192 }
232193
233- # If no differences found, return null
234194 if ($result.Count -eq 0 ) {
235195 return $null
236196 }
237197 } else {
238198 $intuneCollection = Get-Content .\intuneCollection.json | ConvertFrom-Json - ErrorAction SilentlyContinue
239199
240- # Process reference object settings
241200 $referenceItems = $ReferenceObject.settings | ForEach-Object {
242201 $settingInstance = $_.settingInstance
243202 $intuneObj = $intuneCollection | Where-Object { $_.id -eq $settingInstance.settingDefinitionId }
@@ -264,8 +223,6 @@ function Compare-CIPPIntuneObject {
264223 $child.choiceSettingValue.value
265224 }
266225 }
267-
268- # Add object to our temporary list
269226 [PSCustomObject ]@ {
270227 Key = " GroupChild-$ ( $child.settingDefinitionId ) "
271228 Label = $childLabel
@@ -331,7 +288,6 @@ function Compare-CIPPIntuneObject {
331288 $tempOutput
332289 }
333290
334- # Process difference object settings
335291 $differenceItems = $DifferenceObject.settings | ForEach-Object {
336292 $settingInstance = $_.settingInstance
337293 $intuneObj = $intuneCollection | Where-Object { $_.id -eq $settingInstance.settingDefinitionId }
@@ -358,8 +314,6 @@ function Compare-CIPPIntuneObject {
358314 $child.choiceSettingValue.value
359315 }
360316 }
361-
362- # Add object to our temporary list
363317 [PSCustomObject ]@ {
364318 Key = " GroupChild-$ ( $child.settingDefinitionId ) "
365319 Label = $childLabel
@@ -425,17 +379,14 @@ function Compare-CIPPIntuneObject {
425379 $tempOutput
426380 }
427381
428- # Compare the items and create result
429382 $result = [System.Collections.Generic.List [PSObject ]]::new()
430383
431- # Group all items by Key for comparison
432384 $allKeys = @ ($referenceItems | Select-Object - ExpandProperty Key) + @ ($differenceItems | Select-Object - ExpandProperty Key) | Sort-Object - Unique
433385
434386 foreach ($key in $allKeys ) {
435387 $refItem = $referenceItems | Where-Object { $_.Key -eq $key } | Select-Object - First 1
436388 $diffItem = $differenceItems | Where-Object { $_.Key -eq $key } | Select-Object - First 1
437389
438- # Get the setting definition ID from the key
439390 $settingId = $key
440391 if ($key -like ' Simple-*' ) {
441392 $settingId = $key.Substring (7 )
@@ -447,28 +398,22 @@ function Compare-CIPPIntuneObject {
447398 $settingId = $key.Substring (8 )
448399 }
449400
450- # Look up the setting in the collection
451401 $settingDefinition = $intuneCollection | Where-Object { $_.id -eq $settingId }
452402
453- # Get the raw values
454403 $refRawValue = if ($refItem ) { $refItem.Value } else { $null }
455404 $diffRawValue = if ($diffItem ) { $diffItem.Value } else { $null }
456405
457- # Try to translate the values to display names if they're option IDs
458406 $refValue = $refRawValue
459407 $diffValue = $diffRawValue
460408
461- # If the setting has options, try to find the display name for the values
462409 if ($null -ne $settingDefinition -and $null -ne $settingDefinition.options ) {
463- # For reference value
464410 if ($null -ne $refRawValue -and $refRawValue -match ' _\d+$' ) {
465411 $option = $settingDefinition.options | Where-Object { $_.id -eq $refRawValue }
466412 if ($null -ne $option -and $null -ne $option.displayName ) {
467413 $refValue = $option.displayName
468414 }
469415 }
470416
471- # For difference value
472417 if ($null -ne $diffRawValue -and $diffRawValue -match ' _\d+$' ) {
473418 $option = $settingDefinition.options | Where-Object { $_.id -eq $diffRawValue }
474419 if ($null -ne $option -and $null -ne $option.displayName ) {
@@ -477,7 +422,6 @@ function Compare-CIPPIntuneObject {
477422 }
478423 }
479424
480- # Use the display name for the property label if available
481425 $label = if ($null -ne $settingDefinition -and $null -ne $settingDefinition.displayName ) {
482426 $settingDefinition.displayName
483427 } elseif ($refItem ) {
@@ -488,7 +432,6 @@ function Compare-CIPPIntuneObject {
488432 $key
489433 }
490434
491- # Only add to result if values are different or one is missing
492435 if ($refRawValue -ne $diffRawValue -or $null -eq $refRawValue -or $null -eq $diffRawValue ) {
493436 $result.Add ([PSCustomObject ]@ {
494437 Property = $label
@@ -502,4 +445,3 @@ function Compare-CIPPIntuneObject {
502445 }
503446 return $result
504447}
505-
0 commit comments