Skip to content

Commit 2a1f2f0

Browse files
authored
Merge pull request #84 from KelvinTegelaar/master
[pull] master from KelvinTegelaar:master
2 parents d64cb5c + bb940ce commit 2a1f2f0

15 files changed

+162
-136
lines changed
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
function Get-ExoOnlineStringBytes {
2+
param([string]$SizeString)
3+
4+
# This exists because various exo cmdlets like to return a human readable string like "3.322 KB (3,402 bytes)" but not the raw bytes value
5+
6+
if ($SizeString -match '\(([0-9,]+) bytes\)') {
7+
return [int]($Matches[1] -replace ',','')
8+
}
9+
10+
return 0
11+
}

Diff for: Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1

+30-62
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,4 @@
11
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-
#>
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-

Diff for: Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ function Push-ExecScheduledCommand {
116116
'*email*' { Send-CIPPAlert -Type 'email' -Title $title -HTMLContent $HTML -TenantFilter $Tenant }
117117
'*webhook*' {
118118
$Webhook = [PSCustomObject]@{
119+
'tenantId' = $TenantInfo.customerId
119120
'Tenant' = $Tenant
120121
'TaskInfo' = $Item.TaskInfo
121122
'Results' = $Results

Diff for: Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-SchedulerCIPPNotifications.ps1

+12-6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ function Push-SchedulerCIPPNotifications {
3333
}
3434
Write-Information "Alerts: $($Currentlog.count) found"
3535
Write-Information "Standards: $($CurrentStandardsLogs.count) found"
36+
37+
# Get the CIPP URL
38+
$CippConfigTable = Get-CippTable -tablename Config
39+
$CippConfig = Get-CIPPAzDataTableEntity @CippConfigTable -Filter "PartitionKey eq 'InstanceProperties' and RowKey eq 'CIPPURL'"
40+
$CIPPURL = 'https://{0}' -f $CippConfig.Value
41+
3642
#email try
3743
try {
3844
if ($Config.email -like '*@*') {
@@ -42,21 +48,21 @@ function Push-SchedulerCIPPNotifications {
4248
foreach ($tenant in ($CurrentLog.Tenant | Sort-Object -Unique)) {
4349
$Data = ($CurrentLog | Select-Object Message, API, Tenant, Username, Severity | Where-Object -Property tenant -EQ $tenant)
4450
$Subject = "$($Tenant): CIPP Alert: Alerts found starting at $((Get-Date).AddMinutes(-15))"
45-
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table'
51+
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' -CIPPURL $CIPPURL
4652
Send-CIPPAlert -Type 'email' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts'
4753
}
4854
} else {
4955
$Data = ($CurrentLog | Select-Object Message, API, Tenant, Username, Severity)
5056
$Subject = "CIPP Alert: Alerts found starting at $((Get-Date).AddMinutes(-15))"
51-
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table'
57+
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' -CIPPURL $CIPPURL
5258
Send-CIPPAlert -Type 'email' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts'
5359
}
5460
}
5561
if ($CurrentStandardsLogs) {
5662
foreach ($tenant in ($CurrentStandardsLogs.Tenant | Sort-Object -Unique)) {
5763
$Data = ($CurrentStandardsLogs | Where-Object -Property tenant -EQ $tenant)
5864
$Subject = "$($Tenant): Standards are out of sync for $tenant"
59-
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards'
65+
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards' -CIPPURL $CIPPURL
6066
Send-CIPPAlert -Type 'email' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts'
6167
$updateStandards = $CurrentStandardsLogs | ForEach-Object {
6268
if ($_.PSObject.Properties.Name -contains 'sentAsAlert') {
@@ -87,7 +93,7 @@ function Push-SchedulerCIPPNotifications {
8793
}
8894

8995
if ($CurrentStandardsLogs) {
90-
$JSONContent = New-CIPPAlertTemplate -Data $Data -Format 'json' -InputObject 'table'
96+
$JSONContent = New-CIPPAlertTemplate -Data $Data -Format 'json' -InputObject 'table' -CIPPURL $CIPPURL
9197
$CurrentStandardsLogs | ConvertTo-Json -Compress
9298
Send-CIPPAlert -Type 'webhook' -JSONContent $JSONContent -TenantFilter $Tenant -APIName 'Alerts'
9399
$updateStandards = $CurrentStandardsLogs | ForEach-Object {
@@ -110,7 +116,7 @@ function Push-SchedulerCIPPNotifications {
110116
try {
111117
foreach ($tenant in ($CurrentLog.Tenant | Sort-Object -Unique)) {
112118
$Data = ($CurrentLog | Select-Object Message, API, Tenant, Username, Severity | Where-Object -Property tenant -EQ $tenant)
113-
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table'
119+
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' -CIPPURL $CIPPURL
114120
$Title = "$tenant CIPP Alert: Alerts found starting at $((Get-Date).AddMinutes(-15))"
115121
Send-CIPPAlert -Type 'psa' -Title $Title -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts'
116122
$UpdateLogs = $CurrentLog | ForEach-Object { $_.SentAsAlert = $true; $_ }
@@ -119,7 +125,7 @@ function Push-SchedulerCIPPNotifications {
119125
foreach ($standardsTenant in ($CurrentStandardsLogs.Tenant | Sort-Object -Unique)) {
120126
$Data = ($CurrentStandardsLogs | Where-Object -Property tenant -EQ $standardsTenant)
121127
$Subject = "$($standardsTenant): Standards are out of sync for $standardsTenant"
122-
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards'
128+
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards' -CIPPURL $CIPPURL
123129
Send-CIPPAlert -Type 'psa' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $standardsTenant -APIName 'Alerts'
124130
$updateStandards = $CurrentStandardsLogs | ForEach-Object {
125131
if ($_.PSObject.Properties.Name -contains 'sentAsAlert') {

Diff for: Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAccessChecks.ps1

+4-4
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Function Invoke-ExecAccessChecks {
2525
if ($Request.Query.SkipCache -ne 'true' -or $Request.Query.SkipCache -ne $true) {
2626
try {
2727
$Cache = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq 'AccessPermissions' and Timestamp and Timestamp ge datetime'$TimestampFilter'"
28-
$Results = $Cache.Data | ConvertFrom-Json
28+
$Results = $Cache.Data | ConvertFrom-Json -ErrorAction Stop
2929
} catch {
3030
$Results = $null
3131
}
@@ -62,7 +62,7 @@ Function Invoke-ExecAccessChecks {
6262
ExchangeTest = ''
6363
}
6464
if ($TenantCheck) {
65-
$Data = @($TenantCheck.Data | ConvertFrom-Json)
65+
$Data = @($TenantCheck.Data | ConvertFrom-Json -ErrorAction Stop)
6666
$TenantResult.GraphStatus = $Data.GraphStatus
6767
$TenantResult.ExchangeStatus = $Data.ExchangeStatus
6868
$TenantResult.GDAPRoles = $Data.GDAPRoles
@@ -85,7 +85,7 @@ Function Invoke-ExecAccessChecks {
8585
$Results = @()
8686
}
8787
} catch {
88-
Write-Host $_.Exception.Message
88+
Write-Warning "Error running tenant access check - $($_.Exception.Message)"
8989
$Results = @()
9090
}
9191
}
@@ -105,7 +105,7 @@ Function Invoke-ExecAccessChecks {
105105
if (!$Request.Query.SkipCache -eq 'true' -or !$Request.Query.SkipCache -eq $true) {
106106
try {
107107
$Cache = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq 'GDAPRelationships' and Timestamp ge datetime'$TimestampFilter'"
108-
$Results = $Cache.Data | ConvertFrom-Json
108+
$Results = $Cache.Data | ConvertFrom-Json -ErrorAction Stop
109109
} catch {
110110
$Results = $null
111111
}

0 commit comments

Comments
 (0)