Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4f9aa04
fixes bpa sync
KelvinTegelaar Apr 14, 2025
64ff3bb
fix: logging of the user calling the function not working after the h…
kris6673 Apr 14, 2025
6af751d
Tiny headers change
kris6673 Apr 14, 2025
b7b96bf
fix CIPPURL
JohnDuprey Apr 14, 2025
5c8cfa0
fix: add locale validation and fix setting locale if values are not null
kris6673 Apr 14, 2025
39ab54e
max depth
KelvinTegelaar Apr 14, 2025
f02f80d
add short circuit for datetimes and other potential nested objects.
KelvinTegelaar Apr 14, 2025
788e3f1
check settings for each template
JohnDuprey Apr 14, 2025
4cf0f26
Merge pull request #1386 from kris6673/fix-locale
JohnDuprey Apr 14, 2025
f267411
Merge pull request #1385 from kris6673/fix-per-user-mfa-logging
JohnDuprey Apr 14, 2025
bbb646d
add actions to comparelist
JohnDuprey Apr 14, 2025
4c39f6b
replace all . with _
JohnDuprey Apr 14, 2025
dc7545c
Update Set-CIPPStandardsCompareField.ps1
JohnDuprey Apr 14, 2025
92af318
Update Set-CIPPStandardsCompareField.ps1
JohnDuprey Apr 14, 2025
4d8d0f2
fix sanitized table props
JohnDuprey Apr 14, 2025
bf98c86
Create Get-ExoOnlineStringBytes.ps1
rvdwegen Apr 14, 2025
00b1a51
Convert TotalItemSize to bytes first
rvdwegen Apr 14, 2025
14d455f
fix property names
JohnDuprey Apr 15, 2025
7ca41ce
Merge branch 'dev' of https://github.com/KelvinTegelaar/CIPP-API into…
JohnDuprey Apr 15, 2025
e9fed84
readd tenantid to alerts
KelvinTegelaar Apr 15, 2025
b147273
Merge branch 'dev' of https://github.com/KelvinTegelaar/CIPP-API into…
KelvinTegelaar Apr 15, 2025
b32757f
better error handling in access check
JohnDuprey Apr 15, 2025
5e4e482
up version
JohnDuprey Apr 15, 2025
bb940ce
Merge pull request #1389 from KelvinTegelaar/dev
JohnDuprey Apr 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Modules/CIPPCore/Private/Get-ExoOnlineStringBytes.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function Get-ExoOnlineStringBytes {
param([string]$SizeString)

# 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

if ($SizeString -match '\(([0-9,]+) bytes\)') {
return [int]($Matches[1] -replace ',','')
}

return 0
}
92 changes: 30 additions & 62 deletions Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1
Original file line number Diff line number Diff line change
@@ -1,30 +1,4 @@
function Compare-CIPPIntuneObject {
<#
.SYNOPSIS
Compares two Intune objects and returns only the differences.

.DESCRIPTION
This function takes two Intune objects and performs a comparison, returning only the properties that differ.
If no differences are found, it returns null.
It's useful for identifying changes between template objects and existing policies.

.PARAMETER ReferenceObject
The reference Intune object to compare against.

.PARAMETER DifferenceObject
The Intune object to compare with the reference object.

.PARAMETER ExcludeProperties
Additional properties to exclude from the comparison.

.EXAMPLE
$template = Get-CIPPIntunePolicy -tenantFilter $Tenant -DisplayName "Template Policy" -TemplateType "Device"
$existing = Get-CIPPIntunePolicy -tenantFilter $Tenant -DisplayName "Existing Policy" -TemplateType "Device"
$differences = Compare-CIPPIntuneObject -ReferenceObject $template -DifferenceObject $existing

.NOTES
This function performs a comparison of objects, including nested properties.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
Expand All @@ -39,7 +13,6 @@ function Compare-CIPPIntuneObject {
[string[]]$CompareType = @()
)
if ($CompareType -ne 'Catalog') {
# Default properties to exclude from comparison
$defaultExcludeProperties = @(
'id',
'createdDateTime',
Expand All @@ -57,13 +30,9 @@ function Compare-CIPPIntuneObject {
'featureUpdatesPauseStartDate'
)

# Combine default and custom exclude properties
$excludeProps = $defaultExcludeProperties + $ExcludeProperties

# Create a list to store comparison results
$result = [System.Collections.Generic.List[PSObject]]::new()

# Helper function to check if a property should be skipped
function ShouldSkipProperty {
param (
[string]$PropertyName
Expand All @@ -73,7 +42,6 @@ function Compare-CIPPIntuneObject {
$excludeProps -contains $PropertyName)
}

# Recursive function to compare objects deeply
function Compare-ObjectsRecursively {
param (
[Parameter(Mandatory = $true)]
Expand All @@ -83,15 +51,24 @@ function Compare-CIPPIntuneObject {
$Object2,

[Parameter(Mandatory = $false)]
[string]$PropertyPath = ''
[string]$PropertyPath = '',
[int]$Depth = 0,
[int]$MaxDepth = 20
)

# If both objects are null or empty, they're equal
if ($Depth -ge $MaxDepth) {
$result.Add([PSCustomObject]@{
Property = $PropertyPath
ExpectedValue = '[MaxDepthExceeded]'
ReceivedValue = '[MaxDepthExceeded]'
})
return
}

if (($null -eq $Object1 -or $Object1 -eq '') -and ($null -eq $Object2 -or $Object2 -eq '')) {
return
}

# If one object is null but the other isn't, they're different
if (($null -eq $Object1 -or $Object1 -eq '') -xor ($null -eq $Object2 -or $Object2 -eq '')) {
$result.Add([PSCustomObject]@{
Property = $PropertyPath
Expand All @@ -101,7 +78,6 @@ function Compare-CIPPIntuneObject {
return
}

# If objects are of different types, they're different
if ($Object1.GetType() -ne $Object2.GetType()) {
$result.Add([PSCustomObject]@{
Property = $PropertyPath
Expand All @@ -111,9 +87,22 @@ function Compare-CIPPIntuneObject {
return
}

# Handle different object types
# Short-circuit recursion for primitive types
$primitiveTypes = @([double], [decimal], [datetime], [timespan], [guid] )
foreach ($type in $primitiveTypes) {
if ($Object1 -is $type -and $Object2 -is $type) {
if ($Object1 -ne $Object2) {
$result.Add([PSCustomObject]@{
Property = $PropertyPath
ExpectedValue = $Object1
ReceivedValue = $Object2
})
}
return
}
}

if ($Object1 -is [System.Collections.IDictionary]) {
# Compare dictionaries
$allKeys = @($Object1.Keys) + @($Object2.Keys) | Select-Object -Unique

foreach ($key in $allKeys) {
Expand All @@ -122,9 +111,8 @@ function Compare-CIPPIntuneObject {
$newPath = if ($PropertyPath) { "$PropertyPath.$key" } else { $key }

if ($Object1.ContainsKey($key) -and $Object2.ContainsKey($key)) {
#only run if both props are not null
if ($Object1[$key] -and $Object2[$key]) {
Compare-ObjectsRecursively -Object1 $Object1[$key] -Object2 $Object2[$key] -PropertyPath $newPath
Compare-ObjectsRecursively -Object1 $Object1[$key] -Object2 $Object2[$key] -PropertyPath $newPath -Depth ($Depth + 1) -MaxDepth $MaxDepth
}
} elseif ($Object1.ContainsKey($key)) {
$result.Add([PSCustomObject]@{
Expand All @@ -141,14 +129,13 @@ function Compare-CIPPIntuneObject {
}
}
} elseif ($Object1 -is [Array] -or $Object1 -is [System.Collections.IList]) {
# Compare arrays
$maxLength = [Math]::Max($Object1.Count, $Object2.Count)

for ($i = 0; $i -lt $maxLength; $i++) {
$newPath = "$PropertyPath.$i"

if ($i -lt $Object1.Count -and $i -lt $Object2.Count) {
Compare-ObjectsRecursively -Object1 $Object1[$i] -Object2 $Object2[$i] -PropertyPath $newPath
Compare-ObjectsRecursively -Object1 $Object1[$i] -Object2 $Object2[$i] -PropertyPath $newPath -Depth ($Depth + 1) -MaxDepth $MaxDepth
} elseif ($i -lt $Object1.Count) {
$result.Add([PSCustomObject]@{
Property = $newPath
Expand All @@ -164,7 +151,6 @@ function Compare-CIPPIntuneObject {
}
}
} elseif ($Object1 -is [PSCustomObject] -or $Object1.PSObject.Properties.Count -gt 0) {
# Compare PSCustomObjects or objects with properties
$allPropertyNames = @(
$Object1.PSObject.Properties | Select-Object -ExpandProperty Name
$Object2.PSObject.Properties | Select-Object -ExpandProperty Name
Expand All @@ -178,9 +164,8 @@ function Compare-CIPPIntuneObject {
$prop2Exists = $Object2.PSObject.Properties.Name -contains $propName

if ($prop1Exists -and $prop2Exists) {
#only run if both props are not null
if ($Object1.$propName -and $Object2.$propName) {
Compare-ObjectsRecursively -Object1 $Object1.$propName -Object2 $Object2.$propName -PropertyPath $newPath
Compare-ObjectsRecursively -Object1 $Object1.$propName -Object2 $Object2.$propName -PropertyPath $newPath -Depth ($Depth + 1) -MaxDepth $MaxDepth
}
} elseif ($prop1Exists) {
$result.Add([PSCustomObject]@{
Expand All @@ -197,7 +182,6 @@ function Compare-CIPPIntuneObject {
}
}
} else {
# Compare primitive values
$val1 = $Object1.ToString()
$val2 = $Object2.ToString()

Expand All @@ -211,7 +195,6 @@ function Compare-CIPPIntuneObject {
}
}

# Convert objects to PowerShell objects if they're not already
$obj1 = if ($ReferenceObject -is [string]) {
$ReferenceObject | ConvertFrom-Json -AsHashtable -Depth 100
} else {
Expand All @@ -224,13 +207,10 @@ function Compare-CIPPIntuneObject {
$DifferenceObject
}

# Start the recursive comparison
#only do the compare if the objects are not null
if ($obj1 -and $obj2) {
Compare-ObjectsRecursively -Object1 $obj1 -Object2 $obj2
}

# If no differences found, return null
if ($result.Count -eq 0) {
return $null
}
Expand Down Expand Up @@ -425,17 +405,14 @@ function Compare-CIPPIntuneObject {
$tempOutput
}

# Compare the items and create result
$result = [System.Collections.Generic.List[PSObject]]::new()

# Group all items by Key for comparison
$allKeys = @($referenceItems | Select-Object -ExpandProperty Key) + @($differenceItems | Select-Object -ExpandProperty Key) | Sort-Object -Unique

foreach ($key in $allKeys) {
$refItem = $referenceItems | Where-Object { $_.Key -eq $key } | Select-Object -First 1
$diffItem = $differenceItems | Where-Object { $_.Key -eq $key } | Select-Object -First 1

# Get the setting definition ID from the key
$settingId = $key
if ($key -like 'Simple-*') {
$settingId = $key.Substring(7)
Expand All @@ -447,28 +424,22 @@ function Compare-CIPPIntuneObject {
$settingId = $key.Substring(8)
}

# Look up the setting in the collection
$settingDefinition = $intuneCollection | Where-Object { $_.id -eq $settingId }

# Get the raw values
$refRawValue = if ($refItem) { $refItem.Value } else { $null }
$diffRawValue = if ($diffItem) { $diffItem.Value } else { $null }

# Try to translate the values to display names if they're option IDs
$refValue = $refRawValue
$diffValue = $diffRawValue

# If the setting has options, try to find the display name for the values
if ($null -ne $settingDefinition -and $null -ne $settingDefinition.options) {
# For reference value
if ($null -ne $refRawValue -and $refRawValue -match '_\d+$') {
$option = $settingDefinition.options | Where-Object { $_.id -eq $refRawValue }
if ($null -ne $option -and $null -ne $option.displayName) {
$refValue = $option.displayName
}
}

# For difference value
if ($null -ne $diffRawValue -and $diffRawValue -match '_\d+$') {
$option = $settingDefinition.options | Where-Object { $_.id -eq $diffRawValue }
if ($null -ne $option -and $null -ne $option.displayName) {
Expand All @@ -477,7 +448,6 @@ function Compare-CIPPIntuneObject {
}
}

# Use the display name for the property label if available
$label = if ($null -ne $settingDefinition -and $null -ne $settingDefinition.displayName) {
$settingDefinition.displayName
} elseif ($refItem) {
Expand All @@ -488,7 +458,6 @@ function Compare-CIPPIntuneObject {
$key
}

# Only add to result if values are different or one is missing
if ($refRawValue -ne $diffRawValue -or $null -eq $refRawValue -or $null -eq $diffRawValue) {
$result.Add([PSCustomObject]@{
Property = $label
Expand All @@ -502,4 +471,3 @@ function Compare-CIPPIntuneObject {
}
return $result
}

Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ function Push-ExecScheduledCommand {
'*email*' { Send-CIPPAlert -Type 'email' -Title $title -HTMLContent $HTML -TenantFilter $Tenant }
'*webhook*' {
$Webhook = [PSCustomObject]@{
'tenantId' = $TenantInfo.customerId
'Tenant' = $Tenant
'TaskInfo' = $Item.TaskInfo
'Results' = $Results
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ function Push-SchedulerCIPPNotifications {
}
Write-Information "Alerts: $($Currentlog.count) found"
Write-Information "Standards: $($CurrentStandardsLogs.count) found"

# Get the CIPP URL
$CippConfigTable = Get-CippTable -tablename Config
$CippConfig = Get-CIPPAzDataTableEntity @CippConfigTable -Filter "PartitionKey eq 'InstanceProperties' and RowKey eq 'CIPPURL'"
$CIPPURL = 'https://{0}' -f $CippConfig.Value

#email try
try {
if ($Config.email -like '*@*') {
Expand All @@ -42,21 +48,21 @@ function Push-SchedulerCIPPNotifications {
foreach ($tenant in ($CurrentLog.Tenant | Sort-Object -Unique)) {
$Data = ($CurrentLog | Select-Object Message, API, Tenant, Username, Severity | Where-Object -Property tenant -EQ $tenant)
$Subject = "$($Tenant): CIPP Alert: Alerts found starting at $((Get-Date).AddMinutes(-15))"
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table'
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' -CIPPURL $CIPPURL
Send-CIPPAlert -Type 'email' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts'
}
} else {
$Data = ($CurrentLog | Select-Object Message, API, Tenant, Username, Severity)
$Subject = "CIPP Alert: Alerts found starting at $((Get-Date).AddMinutes(-15))"
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table'
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' -CIPPURL $CIPPURL
Send-CIPPAlert -Type 'email' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts'
}
}
if ($CurrentStandardsLogs) {
foreach ($tenant in ($CurrentStandardsLogs.Tenant | Sort-Object -Unique)) {
$Data = ($CurrentStandardsLogs | Where-Object -Property tenant -EQ $tenant)
$Subject = "$($Tenant): Standards are out of sync for $tenant"
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards'
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards' -CIPPURL $CIPPURL
Send-CIPPAlert -Type 'email' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts'
$updateStandards = $CurrentStandardsLogs | ForEach-Object {
if ($_.PSObject.Properties.Name -contains 'sentAsAlert') {
Expand Down Expand Up @@ -87,7 +93,7 @@ function Push-SchedulerCIPPNotifications {
}

if ($CurrentStandardsLogs) {
$JSONContent = New-CIPPAlertTemplate -Data $Data -Format 'json' -InputObject 'table'
$JSONContent = New-CIPPAlertTemplate -Data $Data -Format 'json' -InputObject 'table' -CIPPURL $CIPPURL
$CurrentStandardsLogs | ConvertTo-Json -Compress
Send-CIPPAlert -Type 'webhook' -JSONContent $JSONContent -TenantFilter $Tenant -APIName 'Alerts'
$updateStandards = $CurrentStandardsLogs | ForEach-Object {
Expand All @@ -110,7 +116,7 @@ function Push-SchedulerCIPPNotifications {
try {
foreach ($tenant in ($CurrentLog.Tenant | Sort-Object -Unique)) {
$Data = ($CurrentLog | Select-Object Message, API, Tenant, Username, Severity | Where-Object -Property tenant -EQ $tenant)
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table'
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' -CIPPURL $CIPPURL
$Title = "$tenant CIPP Alert: Alerts found starting at $((Get-Date).AddMinutes(-15))"
Send-CIPPAlert -Type 'psa' -Title $Title -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts'
$UpdateLogs = $CurrentLog | ForEach-Object { $_.SentAsAlert = $true; $_ }
Expand All @@ -119,7 +125,7 @@ function Push-SchedulerCIPPNotifications {
foreach ($standardsTenant in ($CurrentStandardsLogs.Tenant | Sort-Object -Unique)) {
$Data = ($CurrentStandardsLogs | Where-Object -Property tenant -EQ $standardsTenant)
$Subject = "$($standardsTenant): Standards are out of sync for $standardsTenant"
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards'
$HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards' -CIPPURL $CIPPURL
Send-CIPPAlert -Type 'psa' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $standardsTenant -APIName 'Alerts'
$updateStandards = $CurrentStandardsLogs | ForEach-Object {
if ($_.PSObject.Properties.Name -contains 'sentAsAlert') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Function Invoke-ExecAccessChecks {
if ($Request.Query.SkipCache -ne 'true' -or $Request.Query.SkipCache -ne $true) {
try {
$Cache = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq 'AccessPermissions' and Timestamp and Timestamp ge datetime'$TimestampFilter'"
$Results = $Cache.Data | ConvertFrom-Json
$Results = $Cache.Data | ConvertFrom-Json -ErrorAction Stop
} catch {
$Results = $null
}
Expand Down Expand Up @@ -62,7 +62,7 @@ Function Invoke-ExecAccessChecks {
ExchangeTest = ''
}
if ($TenantCheck) {
$Data = @($TenantCheck.Data | ConvertFrom-Json)
$Data = @($TenantCheck.Data | ConvertFrom-Json -ErrorAction Stop)
$TenantResult.GraphStatus = $Data.GraphStatus
$TenantResult.ExchangeStatus = $Data.ExchangeStatus
$TenantResult.GDAPRoles = $Data.GDAPRoles
Expand All @@ -85,7 +85,7 @@ Function Invoke-ExecAccessChecks {
$Results = @()
}
} catch {
Write-Host $_.Exception.Message
Write-Warning "Error running tenant access check - $($_.Exception.Message)"
$Results = @()
}
}
Expand All @@ -105,7 +105,7 @@ Function Invoke-ExecAccessChecks {
if (!$Request.Query.SkipCache -eq 'true' -or !$Request.Query.SkipCache -eq $true) {
try {
$Cache = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq 'GDAPRelationships' and Timestamp ge datetime'$TimestampFilter'"
$Results = $Cache.Data | ConvertFrom-Json
$Results = $Cache.Data | ConvertFrom-Json -ErrorAction Stop
} catch {
$Results = $null
}
Expand Down
Loading