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,7 +42,6 @@ function Compare-CIPPIntuneObject {
7342 $excludeProps -contains $PropertyName )
7443 }
7544
76- # Recursive function to compare objects deeply
7745 function Compare-ObjectsRecursively {
7846 param (
7947 [Parameter (Mandatory = $true )]
@@ -83,15 +51,24 @@ function Compare-CIPPIntuneObject {
8351 $Object2 ,
8452
8553 [Parameter (Mandatory = $false )]
86- [string ]$PropertyPath = ' '
54+ [string ]$PropertyPath = ' ' ,
55+ [int ]$Depth = 0 ,
56+ [int ]$MaxDepth = 20
8757 )
8858
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+
9068 if (($null -eq $Object1 -or $Object1 -eq ' ' ) -and ($null -eq $Object2 -or $Object2 -eq ' ' )) {
9169 return
9270 }
9371
94- # If one object is null but the other isn't, they're different
9572 if (($null -eq $Object1 -or $Object1 -eq ' ' ) -xor ($null -eq $Object2 -or $Object2 -eq ' ' )) {
9673 $result.Add ([PSCustomObject ]@ {
9774 Property = $PropertyPath
@@ -101,7 +78,6 @@ function Compare-CIPPIntuneObject {
10178 return
10279 }
10380
104- # If objects are of different types, they're different
10581 if ($Object1.GetType () -ne $Object2.GetType ()) {
10682 $result.Add ([PSCustomObject ]@ {
10783 Property = $PropertyPath
@@ -111,9 +87,22 @@ function Compare-CIPPIntuneObject {
11187 return
11288 }
11389
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+
115105 if ($Object1 -is [System.Collections.IDictionary ]) {
116- # Compare dictionaries
117106 $allKeys = @ ($Object1.Keys ) + @ ($Object2.Keys ) | Select-Object - Unique
118107
119108 foreach ($key in $allKeys ) {
@@ -122,9 +111,8 @@ function Compare-CIPPIntuneObject {
122111 $newPath = if ($PropertyPath ) { " $PropertyPath .$key " } else { $key }
123112
124113 if ($Object1.ContainsKey ($key ) -and $Object2.ContainsKey ($key )) {
125- # only run if both props are not null
126114 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
128116 }
129117 } elseif ($Object1.ContainsKey ($key )) {
130118 $result.Add ([PSCustomObject ]@ {
@@ -141,14 +129,13 @@ function Compare-CIPPIntuneObject {
141129 }
142130 }
143131 } elseif ($Object1 -is [Array ] -or $Object1 -is [System.Collections.IList ]) {
144- # Compare arrays
145132 $maxLength = [Math ]::Max($Object1.Count , $Object2.Count )
146133
147134 for ($i = 0 ; $i -lt $maxLength ; $i ++ ) {
148135 $newPath = " $PropertyPath .$i "
149136
150137 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
152139 } elseif ($i -lt $Object1.Count ) {
153140 $result.Add ([PSCustomObject ]@ {
154141 Property = $newPath
@@ -164,7 +151,6 @@ function Compare-CIPPIntuneObject {
164151 }
165152 }
166153 } elseif ($Object1 -is [PSCustomObject ] -or $Object1.PSObject.Properties.Count -gt 0 ) {
167- # Compare PSCustomObjects or objects with properties
168154 $allPropertyNames = @ (
169155 $Object1.PSObject.Properties | Select-Object - ExpandProperty Name
170156 $Object2.PSObject.Properties | Select-Object - ExpandProperty Name
@@ -178,9 +164,8 @@ function Compare-CIPPIntuneObject {
178164 $prop2Exists = $Object2.PSObject.Properties.Name -contains $propName
179165
180166 if ($prop1Exists -and $prop2Exists ) {
181- # only run if both props are not null
182167 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
184169 }
185170 } elseif ($prop1Exists ) {
186171 $result.Add ([PSCustomObject ]@ {
@@ -197,7 +182,6 @@ function Compare-CIPPIntuneObject {
197182 }
198183 }
199184 } else {
200- # Compare primitive values
201185 $val1 = $Object1.ToString ()
202186 $val2 = $Object2.ToString ()
203187
@@ -211,7 +195,6 @@ function Compare-CIPPIntuneObject {
211195 }
212196 }
213197
214- # Convert objects to PowerShell objects if they're not already
215198 $obj1 = if ($ReferenceObject -is [string ]) {
216199 $ReferenceObject | ConvertFrom-Json - AsHashtable - Depth 100
217200 } else {
@@ -224,13 +207,10 @@ function Compare-CIPPIntuneObject {
224207 $DifferenceObject
225208 }
226209
227- # Start the recursive comparison
228- # only do the compare if the objects are not null
229210 if ($obj1 -and $obj2 ) {
230211 Compare-ObjectsRecursively - Object1 $obj1 - Object2 $obj2
231212 }
232213
233- # If no differences found, return null
234214 if ($result.Count -eq 0 ) {
235215 return $null
236216 }
@@ -425,17 +405,14 @@ function Compare-CIPPIntuneObject {
425405 $tempOutput
426406 }
427407
428- # Compare the items and create result
429408 $result = [System.Collections.Generic.List [PSObject ]]::new()
430409
431- # Group all items by Key for comparison
432410 $allKeys = @ ($referenceItems | Select-Object - ExpandProperty Key) + @ ($differenceItems | Select-Object - ExpandProperty Key) | Sort-Object - Unique
433411
434412 foreach ($key in $allKeys ) {
435413 $refItem = $referenceItems | Where-Object { $_.Key -eq $key } | Select-Object - First 1
436414 $diffItem = $differenceItems | Where-Object { $_.Key -eq $key } | Select-Object - First 1
437415
438- # Get the setting definition ID from the key
439416 $settingId = $key
440417 if ($key -like ' Simple-*' ) {
441418 $settingId = $key.Substring (7 )
@@ -447,28 +424,22 @@ function Compare-CIPPIntuneObject {
447424 $settingId = $key.Substring (8 )
448425 }
449426
450- # Look up the setting in the collection
451427 $settingDefinition = $intuneCollection | Where-Object { $_.id -eq $settingId }
452428
453- # Get the raw values
454429 $refRawValue = if ($refItem ) { $refItem.Value } else { $null }
455430 $diffRawValue = if ($diffItem ) { $diffItem.Value } else { $null }
456431
457- # Try to translate the values to display names if they're option IDs
458432 $refValue = $refRawValue
459433 $diffValue = $diffRawValue
460434
461- # If the setting has options, try to find the display name for the values
462435 if ($null -ne $settingDefinition -and $null -ne $settingDefinition.options ) {
463- # For reference value
464436 if ($null -ne $refRawValue -and $refRawValue -match ' _\d+$' ) {
465437 $option = $settingDefinition.options | Where-Object { $_.id -eq $refRawValue }
466438 if ($null -ne $option -and $null -ne $option.displayName ) {
467439 $refValue = $option.displayName
468440 }
469441 }
470442
471- # For difference value
472443 if ($null -ne $diffRawValue -and $diffRawValue -match ' _\d+$' ) {
473444 $option = $settingDefinition.options | Where-Object { $_.id -eq $diffRawValue }
474445 if ($null -ne $option -and $null -ne $option.displayName ) {
@@ -477,7 +448,6 @@ function Compare-CIPPIntuneObject {
477448 }
478449 }
479450
480- # Use the display name for the property label if available
481451 $label = if ($null -ne $settingDefinition -and $null -ne $settingDefinition.displayName ) {
482452 $settingDefinition.displayName
483453 } elseif ($refItem ) {
@@ -488,7 +458,6 @@ function Compare-CIPPIntuneObject {
488458 $key
489459 }
490460
491- # Only add to result if values are different or one is missing
492461 if ($refRawValue -ne $diffRawValue -or $null -eq $refRawValue -or $null -eq $diffRawValue ) {
493462 $result.Add ([PSCustomObject ]@ {
494463 Property = $label
@@ -502,4 +471,3 @@ function Compare-CIPPIntuneObject {
502471 }
503472 return $result
504473}
505-
0 commit comments