Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
6b344a5
feat(vacation-mode): add mailbox scheduling, calendar permissions, an…
kris6673 Feb 23, 2026
a7db55f
Add TermInfo to license output and use it
JohnDuprey Feb 27, 2026
113919b
Include servicePlans in license output
JohnDuprey Feb 27, 2026
77ed50e
Use skuId for license ID in tenant sync
JohnDuprey Feb 27, 2026
5786f56
Handle empty Conditional Access Policies
JohnDuprey Feb 27, 2026
39b37d4
feat: Add Invoke-ExecLicenseSearch entrypoint
JohnDuprey Feb 27, 2026
342bca9
Add tenant filtering to universal search
JohnDuprey Feb 27, 2026
9b6ef7f
Add BitLocker key search & caching
JohnDuprey Feb 28, 2026
9c9b3a1
fix: Use UPN instead of UserPrincipalName
JohnDuprey Feb 28, 2026
98d87ba
Refactor technicalNotificationMails handling
ervinswervin Mar 1, 2026
3c6f419
Add JIT reason to alert messages (add/remove)
Zacgoose Mar 1, 2026
7c222e1
up host.json as test
KelvinTegelaar Mar 1, 2026
a2eef25
Merge pull request #1858 from Zacgoose/JIT-reason
KelvinTegelaar Mar 1, 2026
8239002
remove the host.json limits at recommendation of MS docs
KelvinTegelaar Mar 1, 2026
06af2a7
Merge branch 'dev' of https://github.com/KelvinTegelaar/CIPP-API into…
KelvinTegelaar Mar 1, 2026
c6e659a
more experimentation
KelvinTegelaar Mar 1, 2026
a05cd79
unlimited as a test
KelvinTegelaar Mar 1, 2026
c5e50c6
experiment
KelvinTegelaar Mar 1, 2026
904cefe
Moves CA policies.
KelvinTegelaar Mar 1, 2026
5822984
fix: specify type in rerun detection log message
kris6673 Mar 2, 2026
fb8a3be
Add 'None' option for Types in Set-CIPPDBCacheMailboxes
JohnDuprey Mar 2, 2026
30117df
fix(reusable-settings): correct casing and filtering
MWG-Logan Mar 2, 2026
132b977
Add CloudFlare ZTNA authentication support to PwPush integration
Brad-M-K Mar 2, 2026
fdb5d77
fix(reusable-settings): update RAWJson handling in templates
MWG-Logan Mar 2, 2026
9f8e4da
fix(reusable-settings): map setting IDs to template GUIDs
MWG-Logan Mar 2, 2026
a8312e5
Include SMTP clientAppUsed in sign-in filter
JohnDuprey Mar 3, 2026
71516af
Extra Info in MFA scripted alert
Zacgoose Mar 3, 2026
eefce18
Update Get-CIPPAlertNewMFADevice.ps1
Zacgoose Mar 3, 2026
c23d617
fix domain analyser tenant filtering
JohnDuprey Mar 3, 2026
2c11369
force fan out on premium sku
JohnDuprey Mar 3, 2026
ec075cf
clean up logging
JohnDuprey Mar 3, 2026
4ceb270
Merge pull request #5 from KelvinTegelaar/dev
Brad-M-K Mar 3, 2026
f399a26
Add default passphrase support to PwPush link creation
Brad-M-K Mar 3, 2026
20e4879
conditionally add parameters if supplied
JohnDuprey Mar 3, 2026
38a0afc
add position message
JohnDuprey Mar 3, 2026
5b94963
assign results if they exist
JohnDuprey Mar 3, 2026
ad7899a
Update CippEntrypoints.psm1
JohnDuprey Mar 3, 2026
d5082a2
Use generic List for Results instead of array
JohnDuprey Mar 3, 2026
21117da
Merge pull request #1863 from Zacgoose/mfa-alert
KelvinTegelaar Mar 4, 2026
b17e01c
Merge pull request #1861 from MWG-Logan/fix/reusable-settings-2
KelvinTegelaar Mar 4, 2026
1b9db88
fix: correct IpAddress variable in Invoke-ExecCaCheck
kris6673 Mar 4, 2026
6ce2e8e
feat: add support for authentication flow in Invoke-ExecCaCheck
kris6673 Mar 4, 2026
27df789
fix: ensure TermInfo is an array so the frontend looks all nice and p…
kris6673 Mar 4, 2026
ab0319f
Merge pull request #1867 from kris6673/pretty-TermInfo
KelvinTegelaar Mar 4, 2026
714585d
prioritize WEBSITE_RESOURCE_GROUP over OWNER
JohnDuprey Mar 4, 2026
de3732e
feat: add assignment filter support to application assignment
kris6673 Mar 4, 2026
0afb318
Merge pull request #6 from KelvinTegelaar/dev
Brad-M-K Mar 4, 2026
db299fa
fix: mx record alert
JohnDuprey Mar 5, 2026
7d4d6a1
Merge pull request #7 from KelvinTegelaar/dev
Brad-M-K Mar 5, 2026
2264099
Add error handling to PwPush config parsing and remove CloudFlare ZTN…
Brad-M-K Mar 5, 2026
e25adde
fix: Multi-tenant orchestration status
JohnDuprey Mar 5, 2026
d2f067c
Feat: New Standard: Restrict User Device Registration
Zacgoose Mar 5, 2026
ffd3d5a
Merge pull request #1866 from kris6673/ca-test
KelvinTegelaar Mar 5, 2026
2a35361
Introduce Start-CIPPOrchestrator wrapper and migrate callers
JohnDuprey Mar 5, 2026
0dbb6a8
Add cippqueue trigger and output binding
JohnDuprey Mar 5, 2026
8ac3a6a
fix: role permission
JohnDuprey Mar 5, 2026
a40407a
fix: add offload function trigger function to profile
JohnDuprey Mar 5, 2026
2a1ae22
casing
JohnDuprey Mar 6, 2026
1f1402c
casing
JohnDuprey Mar 6, 2026
f671b2b
remove queue output binding due to loading before runspace
JohnDuprey Mar 6, 2026
f4d12ca
Merge pull request #1868 from kris6673/apps-assignmentfilter
KelvinTegelaar Mar 6, 2026
9584a7b
Merge pull request #1869 from Zacgoose/RestrictUserDeviceRegistration
KelvinTegelaar Mar 6, 2026
858e17e
Merge pull request #1857 from ervinswervin/patch-3
KelvinTegelaar Mar 6, 2026
66daa0a
Merge pull request #1860 from kris6673/rerun-type
KelvinTegelaar Mar 6, 2026
2eeba9e
Standard setting stale data
Zacgoose Mar 6, 2026
76b7382
skip offload triggers in local dev
JohnDuprey Mar 6, 2026
cecbed3
refactor: simplify tenant validation and handling in Invoke-ListSched…
JohnDuprey Mar 6, 2026
f63f3d6
Merge pull request #1870 from Zacgoose/standard-setting-stale-data
KelvinTegelaar Mar 6, 2026
888b2c9
Fixes issue with encrypted templates
KelvinTegelaar Mar 6, 2026
9e50dd8
Merge pull request #1865 from Brad-M-K/dev
KelvinTegelaar Mar 6, 2026
a8f6bf1
fix: cast NewDomains and SetDomains to string arrays
kris6673 Mar 7, 2026
0f72af6
refactor: small standardizations
kris6673 Mar 7, 2026
5be8e40
refactor: update task filtering criteria to use 24-hour threshold
JohnDuprey Mar 7, 2026
4f8ae4c
Merge pull request #1871 from kris6673/fix-adddkim
KelvinTegelaar Mar 7, 2026
ee68d51
Merge pull request #1845 from kris6673/vacation
KelvinTegelaar Mar 7, 2026
6a33304
Merge pull request #1872 from kris6673/some-logging-and-stuff
KelvinTegelaar Mar 7, 2026
5156aa0
fix: ensure post-execution alerts are sent only when specified in tas…
JohnDuprey Mar 7, 2026
72fb7bd
feat: enhance Intune compliance handling in Push-CIPPStandardsList fu…
JohnDuprey Mar 7, 2026
0ed29f7
feat: enhance backup restoration process to filter by selected types …
JohnDuprey Mar 8, 2026
67ee365
feat: enhance scheduled task management by adding duplicate name prev…
JohnDuprey Mar 8, 2026
5064682
Feat: Incident Report and Attachment options
Zacgoose Mar 8, 2026
3ecd17c
Tweak :)
Zacgoose Mar 8, 2026
17f3d47
GrantSendOnBehalfTo Permissions Cache
Zacgoose Mar 8, 2026
10349fd
Merge pull request #1875 from Zacgoose/GrantSendOnBehalfTo
KelvinTegelaar Mar 8, 2026
b4009bc
Merge pull request #1874 from Zacgoose/backup-tweak
KelvinTegelaar Mar 8, 2026
2ebd9ff
Merge pull request #1873 from Zacgoose/transport-rule-update
KelvinTegelaar Mar 8, 2026
8f3ce5a
feat: add MFA enforcement check and IncludeDisabled option to MFAAdmi…
kris6673 Mar 8, 2026
99756cb
Merge pull request #1876 from kris6673/issue5448
KelvinTegelaar Mar 8, 2026
a4fa45e
fix: move casting to array to AddRange to fix null checks
kris6673 Mar 9, 2026
0808ea4
Merge pull request #1877 from kris6673/dammit-it-broke-the-null-check
KelvinTegelaar Mar 9, 2026
9ac40f3
clean thingy
KelvinTegelaar Mar 9, 2026
187b2e1
Merge branch 'dev' of https://github.com/KelvinTegelaar/CIPP-API into…
KelvinTegelaar Mar 9, 2026
bc966ea
fixed API name in Set-CIPPDefaultAPDeploymentProfile function
ZenTopBrandon Mar 9, 2026
e4ec5d9
refactor: optimize Intune object comparison
JohnDuprey Mar 9, 2026
750df28
fix: quotes
JohnDuprey Mar 9, 2026
25aaec0
Update Push-CIPPStandardsList.ps1
JohnDuprey Mar 9, 2026
4e23154
Add or update the Azure App Service build and deployment workflow config
KelvinTegelaar Mar 9, 2026
1d01062
Merge pull request #1878 from ZenTopBrandon/bug/autopilot-profile-log…
KelvinTegelaar Mar 9, 2026
ae0b459
Remove the Azure App Service build and deployment workflow config
KelvinTegelaar Mar 9, 2026
f222a09
fix alert comment
KelvinTegelaar Mar 9, 2026
c785d64
Merge branch 'dev' of https://github.com/KelvinTegelaar/CIPP-API into…
KelvinTegelaar Mar 9, 2026
225e06b
feat: Add Compare-CIPPIntuneAssignments and Get-CIPPIntunePolicyAssig…
JohnDuprey Mar 9, 2026
9b587ca
debug logging
JohnDuprey Mar 9, 2026
1d05a9b
chore: bump version to 10.2.0
JohnDuprey Mar 9, 2026
e4a431e
Merge pull request #1879 from KelvinTegelaar/dev
KelvinTegelaar Mar 9, 2026
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
6 changes: 6 additions & 0 deletions CIPPHttpTrigger/function.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
"name": "starter",
"type": "durableClient",
"direction": "in"
},
{
"type": "queue",
"direction": "out",
"name": "QueueItem",
"queueName": "cippqueue"
}
]
}
17 changes: 17 additions & 0 deletions CIPPQueueTrigger/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"scriptFile": "../Modules/CippEntrypoints/CippEntrypoints.psm1",
"entryPoint": "Receive-CippQueueTrigger",
"bindings": [
{
"name": "QueueItem",
"type": "queueTrigger",
"direction": "in",
"queueName": "cippqueue"
},
{
"name": "starter",
"type": "durableClient",
"direction": "in"
}
]
}
64 changes: 48 additions & 16 deletions Modules/CIPPCore/Public/Alerts/Get-CIPPAlertMFAAdmins.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,63 @@ function Get-CIPPAlertMFAAdmins {
}
}
if (!$DuoActive) {
$Users = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/reports/authenticationMethods/userRegistrationDetails?`$top=999&filter=IsAdmin eq true and isMfaRegistered eq false and userType eq 'member'&`$select=id,userDisplayName,userPrincipalName,lastUpdatedDateTime,isMfaRegistered,IsAdmin" -tenantid $($TenantFilter) -AsApp $true |
Where-Object { $_.userDisplayName -ne 'On-Premises Directory Synchronization Service Account' }
$MFAReport = try { Get-CIPPMFAStateReport -TenantFilter $TenantFilter } catch { $null }
$IncludeDisabled = [System.Convert]::ToBoolean($InputValue)

# Filter out JIT admins if any users were found
if ($Users) {
# Check 1: Admins with no MFA registered — prefer cache, fall back to live Graph
$Users = if ($MFAReport) {
$MFAReport | Where-Object { $_.IsAdmin -eq $true -and $_.MFARegistration -eq $false -and ($IncludeDisabled -or $_.AccountEnabled -eq $true) }
} else {
New-GraphGETRequest -uri "https://graph.microsoft.com/beta/reports/authenticationMethods/userRegistrationDetails?`$top=999&filter=IsAdmin eq true and isMfaRegistered eq false and userType eq 'member'&`$select=id,userDisplayName,userPrincipalName,lastUpdatedDateTime,isMfaRegistered,IsAdmin" -tenantid $($TenantFilter) -AsApp $true |
Where-Object { $_.userDisplayName -ne 'On-Premises Directory Synchronization Service Account' } |
Select-Object @{n = 'ID'; e = { $_.id } }, @{n = 'UPN'; e = { $_.userPrincipalName } }, @{n = 'DisplayName'; e = { $_.userDisplayName } }
}

# Check 2: Admins with MFA registered but no enforcement.
# I hate how this ended up looking, but I couldn't think of a better way to do it ¯\_(ツ)_/¯
$UnenforcedAdmins = $MFAReport | Where-Object {
$_.IsAdmin -eq $true -and
$_.MFARegistration -eq $true -and
($IncludeDisabled -or $_.AccountEnabled -eq $true) -and
$_.PerUser -notin @('Enforced', 'Enabled') -and
$null -ne $_.CoveredBySD -and
$_.CoveredBySD -ne $true -and
$_.CoveredByCA -notlike 'Enforced*'
}

# Filter out JIT admins
if ($Users -or $UnenforcedAdmins) {
$Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } | Select-Object -First 1
$JITAdmins = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/users?`$select=id,$($Schema.id)&`$filter=$($Schema.id)/jitAdminEnabled eq true" -tenantid $TenantFilter -ComplexFilter
$JITAdminIds = $JITAdmins.id
$Users = $Users | Where-Object { $_.id -notin $JITAdminIds }
$Users = $Users | Where-Object { $_.ID -notin $JITAdminIds }
$UnenforcedAdmins = $UnenforcedAdmins | Where-Object { $_.ID -notin $JITAdminIds }
}

$AlertData = [System.Collections.Generic.List[PSCustomObject]]::new()

foreach ($user in $Users) {
$AlertData.Add([PSCustomObject]@{
Message = "Admin user $($user.DisplayName) ($($user.UPN)) does not have MFA registered."
UserPrincipalName = $user.UPN
DisplayName = $user.DisplayName
Id = $user.ID
Tenant = $TenantFilter
})
}

if ($Users.UserPrincipalName) {
$AlertData = foreach ($user in $Users) {
[PSCustomObject]@{
Message = "Admin user $($user.userDisplayName) ($($user.userPrincipalName)) does not have MFA registered."
UserPrincipalName = $user.userPrincipalName
DisplayName = $user.userDisplayName
Id = $user.id
LastUpdated = $user.lastUpdatedDateTime
foreach ($user in $UnenforcedAdmins) {
$AlertData.Add([PSCustomObject]@{
Message = "Admin user $($user.DisplayName) ($($user.UPN)) has MFA registered but no enforcement method (Per-User MFA, Security Defaults, or Conditional Access) is active."
UserPrincipalName = $user.UPN
DisplayName = $user.DisplayName
Id = $user.ID
Tenant = $TenantFilter
}
}
})
}

if ($AlertData.Count -gt 0) {
Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData

}
} else {
Write-LogMessage -message 'Potentially using Duo for MFA, could not check MFA status for Admins with 100% accuracy' -API 'MFA Alerts - Informational' -tenant $TenantFilter -sev Info
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ function Get-CIPPAlertMXRecordChanged {
$CacheTable = Get-CippTable -tablename 'CacheMxRecords'
$PreviousResults = Get-CIPPAzDataTableEntity @CacheTable -Filter "PartitionKey eq '$TenantFilter'"

if (!$DomainData) {
return
}

$ChangedDomains = foreach ($Domain in $DomainData) {
try {
$PreviousDomain = $PreviousResults | Where-Object { $_.Domain -eq $Domain.Domain }
Expand Down Expand Up @@ -60,8 +64,6 @@ function Get-CIPPAlertMXRecordChanged {
if ($ChangedDomains) {
Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $ChangedDomains
}
return $true

} catch {
Write-LogMessage -message "Failed to check MX record changes: $($_.Exception.Message)" -API 'MX Record Alert' -tenant $TenantFilter -sev Error
}
Expand Down
29 changes: 23 additions & 6 deletions Modules/CIPPCore/Public/Alerts/Get-CIPPAlertNewMFADevice.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,30 @@ function Get-CIPPAlertNewMFADevice {
$User = $Log.targetResources[0].userPrincipalName
if (-not $User) { $User = $Log.initiatedBy.user.userPrincipalName }

$IPAddress = $Log.initiatedBy.user.ipAddress
$LocationData = $null
if (-not [string]::IsNullOrEmpty($IPAddress) -and $IPAddress -notmatch '[X]+') {
try {
$LocationData = Get-CIPPGeoIPLocation -IP $IPAddress
} catch {
Write-Information "Could not enrich MFA audit IP ${$IPAddress}: $($_.Exception.Message)"
}
}

[PSCustomObject]@{
Message = "New MFA method registered: $User"
User = $User
DisplayName = $Log.targetResources[0].displayName
Activity = $Log.activityDisplayName
ActivityTime = $Log.activityDateTime
Tenant = $TenantFilter
Message = "New MFA method registered: $User"
User = $User
DisplayName = $Log.targetResources[0].displayName
Activity = $Log.activityDisplayName
ActivityTime = $Log.activityDateTime
Tenant = $TenantFilter
IpAddress = $IPAddress
CountryOrRegion = if ($LocationData) { $LocationData.countryCode } else { $null }
City = if ($LocationData) { $LocationData.city } else { $null }
Proxy = if ($LocationData) { $LocationData.proxy } else { $null }
Hosting = if ($LocationData) { $LocationData.hosting } else { $null }
ASN = if ($LocationData) { $LocationData.asname } else { $null }
GeoLocationInfo = if ($LocationData) { ($LocationData | ConvertTo-Json -Depth 10 -Compress) } else { $null }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function Get-CIPPAlertSmtpAuthSuccess {

try {
# Graph API endpoint for sign-ins
$uri = "https://graph.microsoft.com/v1.0/auditLogs/signIns?`$filter=clientAppUsed eq 'Authenticated SMTP' and status/errorCode eq 0"
$uri = "https://graph.microsoft.com/v1.0/auditLogs/signIns?`$filter=(clientAppUsed eq 'Authenticated SMTP' or clientAppUsed eq 'SMTP') and status/errorCode eq 0"

# Call Graph API for the given tenant
$SignIns = New-GraphGetRequest -uri $uri -tenantid $TenantFilter
Expand Down
134 changes: 134 additions & 0 deletions Modules/CIPPCore/Public/Compare-CIPPIntuneAssignments.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
function Compare-CIPPIntuneAssignments {
<#
.SYNOPSIS
Compares existing Intune policy assignments against expected assignment settings.
.DESCRIPTION
Returns $true if the existing assignments match the expected settings, $false if they differ,
or $null if the comparison could not be completed (e.g. Graph error).
.PARAMETER ExistingAssignments
The current assignments on the policy, as returned by Get-CIPPIntunePolicyAssignments.
.PARAMETER ExpectedAssignTo
The expected assignment target type: allLicensedUsers, AllDevices, AllDevicesAndUsers,
customGroup, or On (no assignment).
.PARAMETER ExpectedCustomGroup
The expected custom group name(s), comma-separated. Used when ExpectedAssignTo is 'customGroup'.
.PARAMETER ExpectedExcludeGroup
The expected exclusion group name(s), comma-separated.
.PARAMETER ExpectedAssignmentFilter
The expected assignment filter display name. Wildcards supported.
.PARAMETER ExpectedAssignmentFilterType
'include' or 'exclude'. Defaults to 'include'.
.PARAMETER TenantFilter
The tenant to query for group/filter resolution.
.FUNCTIONALITY
Internal
#>
param(
[object[]]$ExistingAssignments,
[string]$ExpectedAssignTo,
[string]$ExpectedCustomGroup,
[string]$ExpectedExcludeGroup,
[string]$ExpectedAssignmentFilter,
[string]$ExpectedAssignmentFilterType = 'include',
[Parameter(Mandatory = $true)]
[string]$TenantFilter
)

try {
# Normalize existing targets
$ExistingTargetTypes = @($ExistingAssignments.target.'@odata.type' | Where-Object { $_ })
$ExistingIncludeGroupIds = @(
$ExistingAssignments |
Where-Object { $_.target.'@odata.type' -eq '#microsoft.graph.groupAssignmentTarget' } |
ForEach-Object { $_.target.groupId }
)
$ExistingExcludeGroupIds = @(
$ExistingAssignments |
Where-Object { $_.target.'@odata.type' -eq '#microsoft.graph.exclusionGroupAssignmentTarget' } |
ForEach-Object { $_.target.groupId }
)

# Determine expected include target types
$ExpectedIncludeTypes = switch ($ExpectedAssignTo) {
'allLicensedUsers' { @('#microsoft.graph.allLicensedUsersAssignmentTarget') }
'AllDevices' { @('#microsoft.graph.allDevicesAssignmentTarget') }
'AllDevicesAndUsers' { @('#microsoft.graph.allDevicesAssignmentTarget', '#microsoft.graph.allLicensedUsersAssignmentTarget') }
'customGroup' { @('#microsoft.graph.groupAssignmentTarget') }
'On' { @() }
default { @() }
}

# Compare include target types (ignore exclusion targets)
$ExistingIncludeTypes = @($ExistingTargetTypes | Where-Object { $_ -ne '#microsoft.graph.exclusionGroupAssignmentTarget' })
$TargetTypeMatch = $true
foreach ($t in $ExpectedIncludeTypes) {
if ($t -notin $ExistingIncludeTypes) { $TargetTypeMatch = $false; break }
}
if ($TargetTypeMatch) {
foreach ($t in $ExistingIncludeTypes) {
if ($t -notin $ExpectedIncludeTypes) { $TargetTypeMatch = $false; break }
}
}

# Lazy-load groups cache only if needed
$AllGroupsCache = $null

# For custom groups, resolve names to IDs and compare
$IncludeGroupMatch = $true
if ($ExpectedAssignTo -eq 'customGroup' -and $ExpectedCustomGroup) {
$AllGroupsCache = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$select=id,displayName&$top=999' -tenantid $TenantFilter
$ExpectedGroupIds = @(
$ExpectedCustomGroup.Split(',').Trim() | ForEach-Object {
$name = $_
$AllGroupsCache | Where-Object { $_.displayName -like $name } | Select-Object -ExpandProperty id
} | Where-Object { $_ }
)
$MissingIds = @($ExpectedGroupIds | Where-Object { $_ -notin $ExistingIncludeGroupIds })
$ExtraIds = @($ExistingIncludeGroupIds | Where-Object { $_ -notin $ExpectedGroupIds })
$IncludeGroupMatch = ($MissingIds.Count -eq 0 -and $ExtraIds.Count -eq 0)
}

# Compare exclusion groups
$ExcludeGroupMatch = $true
if ($ExpectedExcludeGroup) {
if (-not $AllGroupsCache) {
$AllGroupsCache = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$select=id,displayName&$top=999' -tenantid $TenantFilter
}
$ExpectedExcludeIds = @(
$ExpectedExcludeGroup.Split(',').Trim() | ForEach-Object {
$name = $_
$AllGroupsCache | Where-Object { $_.displayName -like $name } | Select-Object -ExpandProperty id
} | Where-Object { $_ }
)
$MissingExcludeIds = @($ExpectedExcludeIds | Where-Object { $_ -notin $ExistingExcludeGroupIds })
$ExtraExcludeIds = @($ExistingExcludeGroupIds | Where-Object { $_ -notin $ExpectedExcludeIds })
$ExcludeGroupMatch = ($MissingExcludeIds.Count -eq 0 -and $ExtraExcludeIds.Count -eq 0)
} elseif ($ExistingExcludeGroupIds.Count -gt 0) {
# No exclusions expected but some exist
$ExcludeGroupMatch = $false
}

# Compare assignment filter
$FilterMatch = $true
if ($ExpectedAssignmentFilter) {
$ExistingFilterIds = @(
$ExistingAssignments |
Where-Object { $_.target.deviceAndAppManagementAssignmentFilterId } |
ForEach-Object { $_.target.deviceAndAppManagementAssignmentFilterId }
)
if ($ExistingFilterIds.Count -eq 0) {
$FilterMatch = $false
} else {
$AllFilters = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/assignmentFilters' -tenantid $TenantFilter
$ExpectedFilter = $AllFilters | Where-Object { $_.displayName -like $ExpectedAssignmentFilter } | Select-Object -First 1
$FilterMatch = $ExpectedFilter -and ($ExpectedFilter.id -in $ExistingFilterIds)
}
}

return $TargetTypeMatch -and $IncludeGroupMatch -and $ExcludeGroupMatch -and $FilterMatch

} catch {
Write-Warning "Compare-CIPPIntuneAssignments failed for tenant $TenantFilter : $($_.Exception.Message)"
return $null # null = unknown, don't treat as mismatch
}
}
Loading