Skip to content

Commit e4ec5d9

Browse files
committed
refactor: optimize Intune object comparison
1 parent 187b2e1 commit e4ec5d9

5 files changed

Lines changed: 83 additions & 47 deletions

File tree

Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,11 @@ function Compare-CIPPIntuneObject {
340340
}
341341
} else {
342342
$intuneCollection = Get-Content .\intuneCollection.json | ConvertFrom-Json -ErrorAction SilentlyContinue
343+
# Build a hashtable index for O(1) lookups instead of O(n) Where-Object scans
344+
$intuneCollectionIndex = @{}
345+
foreach ($item in $intuneCollection) {
346+
if ($item.id) { $intuneCollectionIndex[$item.id] = $item }
347+
}
343348

344349
# Recursive function to process group setting collections at any depth
345350
function Process-GroupSettingChildren {
@@ -349,13 +354,13 @@ function Compare-CIPPIntuneObject {
349354
[Parameter(Mandatory = $true)]
350355
[string]$Source,
351356
[Parameter(Mandatory = $true)]
352-
$IntuneCollection
357+
$IntuneCollectionIndex
353358
)
354359

355360
$results = [System.Collections.Generic.List[PSCustomObject]]::new()
356361

357362
foreach ($child in $Children) {
358-
$childIntuneObj = $IntuneCollection | Where-Object { $_.id -eq $child.settingDefinitionId }
363+
$childIntuneObj = $IntuneCollectionIndex[$child.settingDefinitionId]
359364
$childLabel = if ($childIntuneObj?.displayName) {
360365
$childIntuneObj.displayName
361366
} else {
@@ -367,7 +372,7 @@ function Compare-CIPPIntuneObject {
367372
if ($child.groupSettingCollectionValue) {
368373
foreach ($groupValue in $child.groupSettingCollectionValue) {
369374
if ($groupValue.children) {
370-
$nestedResults = Process-GroupSettingChildren -Children $groupValue.children -Source $Source -IntuneCollection $IntuneCollection
375+
$nestedResults = Process-GroupSettingChildren -Children $groupValue.children -Source $Source -IntuneCollectionIndex $IntuneCollectionIndex
371376
foreach ($nr in $nestedResults) { $results.Add($nr) }
372377
}
373378
}
@@ -453,7 +458,7 @@ function Compare-CIPPIntuneObject {
453458

454459
# Also process any children within choice setting values
455460
if ($child.choiceSettingValue?.children) {
456-
$nestedResults = Process-GroupSettingChildren -Children $child.choiceSettingValue.children -Source $Source -IntuneCollection $IntuneCollection
461+
$nestedResults = Process-GroupSettingChildren -Children $child.choiceSettingValue.children -Source $Source -IntuneCollectionIndex $IntuneCollectionIndex
457462
foreach ($nr in $nestedResults) { $results.Add($nr) }
458463
}
459464
}
@@ -464,14 +469,14 @@ function Compare-CIPPIntuneObject {
464469
# Process reference object settings
465470
$referenceItems = $ReferenceObject.settings | ForEach-Object {
466471
$settingInstance = $_.settingInstance
467-
$intuneObj = $intuneCollection | Where-Object { $_.id -eq $settingInstance.settingDefinitionId }
472+
$intuneObj = $intuneCollectionIndex[$settingInstance.settingDefinitionId]
468473
$tempOutput = switch ($settingInstance.'@odata.type') {
469474
'#microsoft.graph.deviceManagementConfigurationGroupSettingCollectionInstance' {
470475
if ($null -ne $settingInstance.groupSettingCollectionValue) {
471476
$groupResults = [System.Collections.Generic.List[PSCustomObject]]::new()
472477
foreach ($groupValue in $settingInstance.groupSettingCollectionValue) {
473478
if ($groupValue.children -is [System.Array]) {
474-
$childResults = Process-GroupSettingChildren -Children $groupValue.children -Source 'Reference' -IntuneCollection $intuneCollection
479+
$childResults = Process-GroupSettingChildren -Children $groupValue.children -Source 'Reference' -IntuneCollectionIndex $intuneCollectionIndex
475480
foreach ($cr in $childResults) { $groupResults.Add($cr) }
476481
}
477482
}
@@ -536,14 +541,14 @@ function Compare-CIPPIntuneObject {
536541
# Process difference object settings
537542
$differenceItems = $DifferenceObject.settings | ForEach-Object {
538543
$settingInstance = $_.settingInstance
539-
$intuneObj = $intuneCollection | Where-Object { $_.id -eq $settingInstance.settingDefinitionId }
544+
$intuneObj = $intuneCollectionIndex[$settingInstance.settingDefinitionId]
540545
$tempOutput = switch ($settingInstance.'@odata.type') {
541546
'#microsoft.graph.deviceManagementConfigurationGroupSettingCollectionInstance' {
542547
if ($null -ne $settingInstance.groupSettingCollectionValue) {
543548
$groupResults = [System.Collections.Generic.List[PSCustomObject]]::new()
544549
foreach ($groupValue in $settingInstance.groupSettingCollectionValue) {
545550
if ($groupValue.children -is [System.Array]) {
546-
$childResults = Process-GroupSettingChildren -Children $groupValue.children -Source 'Difference' -IntuneCollection $intuneCollection
551+
$childResults = Process-GroupSettingChildren -Children $groupValue.children -Source 'Difference' -IntuneCollectionIndex $intuneCollectionIndex
547552
foreach ($cr in $childResults) { $groupResults.Add($cr) }
548553
}
549554
}
@@ -624,7 +629,7 @@ function Compare-CIPPIntuneObject {
624629
$settingId = $key.Substring(8)
625630
}
626631

627-
$settingDefinition = $intuneCollection | Where-Object { $_.id -eq $settingId }
632+
$settingDefinition = $intuneCollectionIndex[$settingId]
628633

629634
$refRawValue = if ($refItem) { $refItem.Value } else { $null }
630635
$diffRawValue = if ($diffItem) { $diffItem.Value } else { $null }

Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandardsList.ps1

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,16 @@ function Push-CIPPStandardsList {
6464
} else {
6565
# License valid - check policy timestamps to filter unchanged templates
6666
$TypeMap = @{
67-
Device = 'deviceManagement/deviceConfigurations'
68-
Catalog = 'deviceManagement/configurationPolicies'
69-
Admin = 'deviceManagement/groupPolicyConfigurations'
70-
deviceCompliancePolicies = 'deviceManagement/deviceCompliancePolicies'
71-
AppProtection_Android = 'deviceAppManagement/androidManagedAppProtections'
72-
AppProtection_iOS = 'deviceAppManagement/iosManagedAppProtections'
67+
Device = 'deviceManagement/deviceConfigurations'
68+
Catalog = 'deviceManagement/configurationPolicies'
69+
Admin = 'deviceManagement/groupPolicyConfigurations'
70+
deviceCompliancePolicies = 'deviceManagement/deviceCompliancePolicies'
71+
AppProtection_Android = 'deviceAppManagement/androidManagedAppProtections'
72+
AppProtection_iOS = 'deviceAppManagement/iosManagedAppProtections'
73+
windowsDriverUpdateProfiles = 'deviceManagement/windowsDriverUpdateProfiles'
74+
windowsFeatureUpdateProfiles = 'deviceManagement/windowsFeatureUpdateProfiles'
75+
windowsQualityUpdatePolicies = 'deviceManagement/windowsQualityUpdatePolicies'
76+
windowsQualityUpdateProfiles = 'deviceManagement/windowsQualityUpdateProfiles'
7377
}
7478

7579
$BulkRequests = $TypeMap.GetEnumerator() | ForEach-Object {
@@ -86,8 +90,9 @@ function Push-CIPPStandardsList {
8690
$PolicyTimestamps = @{}
8791

8892
foreach ($Result in $BulkResults) {
89-
$GraphTime = $Result.body.value[0].lastModifiedDateTime
90-
$GraphId = $Result.body.value[0].id
93+
$FirstPolicy = if ($Result.body.value) { $Result.body.value[0] } else { $null }
94+
$GraphTime = $FirstPolicy.lastModifiedDateTime
95+
$GraphId = $FirstPolicy.id
9196
$GraphCount = ($Result.body.value | Measure-Object).Count
9297
$Cached = Get-CIPPAzDataTableEntity @TrackingTable -Filter "PartitionKey eq '$TenantFilter' and RowKey eq '$($Result.id)'"
9398

@@ -117,8 +122,24 @@ function Push-CIPPStandardsList {
117122
LatestPolicyId = $GraphId
118123
PolicyCount = $GraphCount
119124
} -Force | Out-Null
125+
} elseif ($Cached -and $Cached.PolicyCount -ne $null) {
126+
# No timestamp available - fall back to count-based detection
127+
$Changed = $CountChanged -or $IdChanged
128+
Add-CIPPAzDataTableEntity @TrackingTable -Entity @{
129+
PartitionKey = $TenantFilter
130+
RowKey = $Result.id
131+
LatestPolicyId = $GraphId
132+
PolicyCount = $GraphCount
133+
} -Force | Out-Null
120134
} else {
135+
# No timestamp and no prior cache entry - treat as changed and seed the cache
121136
$Changed = $true
137+
Add-CIPPAzDataTableEntity @TrackingTable -Entity @{
138+
PartitionKey = $TenantFilter
139+
RowKey = $Result.id
140+
LatestPolicyId = $GraphId
141+
PolicyCount = $GraphCount
142+
} -Force | Out-Null
122143
}
123144

124145
$PolicyTimestamps[$Result.id] = $Changed
@@ -160,7 +181,7 @@ function Push-CIPPStandardsList {
160181
}
161182

162183
# Check StandardTemplate changes
163-
$StandardTemplate = Get-CIPPAzDataTableEntity @StandardTemplateTable -Filter "PartitionKey eq 'StandardsTemplateV2' and RowKey eq '$($Template.TemplateId)'"
184+
$StandardTemplate = Get-CIPPAzDataTableEntity @TemplateTable -Filter "PartitionKey eq 'StandardsTemplateV2' and RowKey eq '$($Template.TemplateId)'"
164185
$StandardTemplateChanged = $false
165186

166187
if ($StandardTemplate) {
@@ -182,7 +203,7 @@ function Push-CIPPStandardsList {
182203
} -Force | Out-Null
183204
}
184205

185-
# Remove or downgrade based on change state and compliance
206+
# Remove cosed onmpliance
186207
if (-not $PolicyChanged -and -not $StandardTemplateChanged) {
187208
$AlignmentKey = "standards.IntuneTemplate.$($Template.Settings.TemplateList.value)"
188209
$IsDeployed = $IntuneComplianceLookup.ContainsKey($AlignmentKey)
@@ -192,15 +213,6 @@ function Push-CIPPStandardsList {
192213
# Policy unchanged and compliant - no action needed
193214
Write-Host "NO INTUNE CHANGE: Filtering out $Key for $TenantFilter (compliant)"
194215
[void]$ComputedStandards.Remove($Key)
195-
} elseif ($IsDeployed) {
196-
# Policy deployed but drifted, and nothing changed - report only, don't force remediate
197-
Write-Host "COMPLIANCE DRIFT: Downgrading $Key to Report mode for $TenantFilter (deployed, not compliant, no changes)"
198-
$ComputedStandards[$Key].Settings | Add-Member -NotePropertyName 'remediate' -NotePropertyValue $false -Force
199-
$ComputedStandards[$Key].Settings | Add-Member -NotePropertyName 'report' -NotePropertyValue $true -Force
200-
} else {
201-
# No alignment data yet - policy not yet deployed, skip (will run on next cycle with changes)
202-
Write-Host "NO INTUNE CHANGE: Filtering out $Key for $TenantFilter (not yet deployed, no changes)"
203-
[void]$ComputedStandards.Remove($Key)
204216
}
205217
}
206218
}
@@ -237,27 +249,28 @@ function Push-CIPPStandardsList {
237249
Write-Information "Updating CIPPDB cache for Conditional Access policies for $TenantFilter"
238250
Set-CIPPDBCacheConditionalAccessPolicies -TenantFilter $TenantFilter
239251
} catch {
240-
Write-Warning "Failed to update CA cache for $TenantFilter : $($_.Exception.Message)"
252+
Write-Warning "Failed to update CA cache for $TenantFilter : $($_.Exception.Message)'
241253
}
242254
}
243255
}
244256
245-
Write-Host "Returning $($ComputedStandards.Count) standards for tenant $TenantFilter after filtering."
246-
# Return filtered standards
247-
$FilteredStandards = $ComputedStandards.Values | ForEach-Object {
248-
[PSCustomObject]@{
249-
Tenant = $_.Tenant
250-
Standard = $_.Standard
251-
Settings = $_.Settings
252-
TemplateId = $_.TemplateId
253-
FunctionName = 'CIPPStandard'
254-
}
255-
}
256-
Write-Host "Sending back $($FilteredStandards.Count) standards: $($FilteredStandards | ConvertTo-Json -Depth 5 -Compress)"
257-
return $FilteredStandards
257+
Write-Host 'Returning $($ComputedStandards.Count) standards for tenant $TenantFilter after filtering."
258+
# Return filtered standards
259+
$FilteredStandards = $ComputedStandards.Values | ForEach-Object {
260+
[PSCustomObject]@{
261+
Tenant = $_.Tenant
262+
Standard = $_.Standard
263+
Settings = $_.Settings
264+
TemplateId = $_.TemplateId
265+
FunctionName = 'CIPPStandard'
266+
}
267+
}
268+
Write-Host "Sending back $($FilteredStandards.Count) standards: $($FilteredStandards | ConvertTo-Json -Depth 5 -Compress)"
269+
return @($FilteredStandards)
258270

259-
} catch {
260-
Write-Warning "Error listing standards for $TenantFilter : $($_.Exception.Message)"
271+
} catch {
272+
Write-Warning "Error listing standards for $TenantFilter : $($_.Exception.Message)'
261273
return @()
262274
}
263275
}
276+
'

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveStandardTemplate.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ function Invoke-RemoveStandardTemplate {
1616
$ID = $Request.Body.ID ?? $Request.Query.ID
1717
try {
1818
$Table = Get-CippTable -tablename 'templates'
19-
$Filter = "PartitionKey eq 'StandardsTemplateV2' and GUID eq '$ID'"
20-
$ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey, JSON
19+
$Filter = "PartitionKey eq 'StandardsTemplateV2' and (GUID eq '$ID' or RowKey eq '$ID' or OriginalEntityId eq '$ID')"
20+
$ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey, ETag, JSON
2121
if ($ClearRow.JSON) {
2222
$TemplateName = (ConvertFrom-Json -InputObject $ClearRow.JSON -ErrorAction SilentlyContinue).templateName
2323
} else {

Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ function Get-CIPPTenantAlignment {
3434
$JSON = $_.JSON
3535
try {
3636
$RowKey = $_.RowKey
37+
if ([string]::IsNullOrWhiteSpace($JSON)) { return }
3738
$Data = $JSON | ConvertFrom-Json -Depth 100 -ErrorAction Stop
3839
} catch {
3940
Write-Warning "$($RowKey) standard could not be loaded: $($_.Exception.Message)"

Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,16 @@ function Invoke-CIPPStandardIntuneTemplate {
3838
param($Tenant, $Settings)
3939

4040
Write-Host 'INTUNETEMPLATERUN'
41+
$sw = [System.Diagnostics.Stopwatch]::StartNew()
42+
$lap = $sw.Elapsed
43+
4144
$Table = Get-CippTable -tablename 'templates'
4245
$Filter = "PartitionKey eq 'IntuneTemplate'"
4346

4447
$Template = (Get-CIPPAzDataTableEntity @Table -Filter $Filter | Where-Object -Property RowKey -Like "$($Settings.TemplateList.value)*").JSON | ConvertFrom-Json -ErrorAction SilentlyContinue
48+
Write-Information "[IntuneTemplate][$Tenant] TableLoad: $([int]($sw.Elapsed - $lap).TotalMilliseconds)ms"
49+
$lap = $sw.Elapsed
50+
4551
if ($null -eq $Template) {
4652
Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to find template $($Settings.TemplateList.value). Has this Intune Template been deleted?" -sev 'Error'
4753
return $true
@@ -58,6 +64,8 @@ function Invoke-CIPPStandardIntuneTemplate {
5864
Write-Host "IntuneTemplate: $($Settings.TemplateList.value) - Failed to sync reusable policy settings. Skipping this template."
5965
return $true
6066
}
67+
Write-Information "[IntuneTemplate][$Tenant] ReusableSync: $([int]($sw.Elapsed - $lap).TotalMilliseconds)ms"
68+
$lap = $sw.Elapsed
6169

6270
$displayname = $Template.Displayname
6371
$description = $Template.Description
@@ -69,16 +77,19 @@ function Invoke-CIPPStandardIntuneTemplate {
6977
} catch {
7078
$ExistingPolicy = $null
7179
}
80+
Write-Information "[IntuneTemplate][$Tenant] GetPolicy '$displayname' ($TemplateType): $([int]($sw.Elapsed - $lap).TotalMilliseconds)ms"
81+
$lap = $sw.Elapsed
7282

7383
if ($ExistingPolicy) {
7484
try {
7585
$RawJSON = Get-CIPPTextReplacement -Text $RawJSON -TenantFilter $Tenant
7686
$JSONExistingPolicy = $ExistingPolicy.cippconfiguration | ConvertFrom-Json
7787
$JSONTemplate = $RawJSON | ConvertFrom-Json
78-
#This might be a slow one.
7988
$Compare = Compare-CIPPIntuneObject -ReferenceObject $JSONTemplate -DifferenceObject $JSONExistingPolicy -compareType $TemplateType -ErrorAction SilentlyContinue
8089
} catch {
8190
}
91+
Write-Information "[IntuneTemplate][$Tenant] Compare '$displayname': $([int]($sw.Elapsed - $lap).TotalMilliseconds)ms"
92+
$lap = $sw.Elapsed
8293
} else {
8394
$compare = [pscustomobject]@{
8495
MatchFailed = $true
@@ -128,7 +139,10 @@ function Invoke-CIPPStandardIntuneTemplate {
128139
} catch {
129140
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
130141
Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to create or update Intune Template $($CompareResult.displayname), Error: $ErrorMessage" -sev 'Error'
142+
Write-Information "IntuneTemplate: $($CompareResult.displayname) - Failed to remediate. Error: $ErrorMessage"
131143
}
144+
Write-Information "[IntuneTemplate][$Tenant] Remediate '$displayname': $([int]($sw.Elapsed - $lap).TotalMilliseconds)ms"
145+
$lap = $sw.Elapsed
132146
}
133147

134148
if ($Settings.alert) {
@@ -162,4 +176,7 @@ function Invoke-CIPPStandardIntuneTemplate {
162176
Set-CIPPStandardsCompareField -FieldName "standards.IntuneTemplate.$id" -CurrentValue $CurrentValue -ExpectedValue $ExpectedValue -TenantFilter $Tenant
163177
#Add-CIPPBPAField -FieldName "policy-$id" -FieldValue $Compare -StoreAs bool -Tenant $tenant
164178
}
179+
180+
$sw.Stop()
181+
Write-Information "[IntuneTemplate][$Tenant] TOTAL '$displayname': $([int]$sw.Elapsed.TotalMilliseconds)ms"
165182
}

0 commit comments

Comments
 (0)