Skip to content

Commit 01969a2

Browse files
authored
Merge pull request #1064 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents f236956 + bb1e10d commit 01969a2

9 files changed

Lines changed: 378 additions & 51 deletions

File tree

Config/standards.json

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1656,27 +1656,38 @@
16561656
"name": "standards.StaleEntraDevices",
16571657
"cat": "Entra (AAD) Standards",
16581658
"tag": ["Essential 8 (1501)", "NIST CSF 2.0 (ID.AM-08)", "NIST CSF 2.0 (PR.PS-03)"],
1659-
"helpText": "**Remediate is currently not available**. Cleans up Entra devices that have not connected/signed in for the specified number of days.",
1660-
"docsDescription": "Remediate is currently not available. Cleans up Entra devices that have not connected/signed in for the specified number of days. First disables and later deletes the devices. More info can be found in the [Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity/devices/manage-stale-devices)",
1659+
"helpText": "Cleans up Entra devices that have not connected/signed in for the specified number of days. Remediation first disables stale enabled devices and, on a later run, deletes stale devices that are already disabled. Hybrid-joined, Intune-managed and Autopilot devices are skipped. Deleting a device permanently removes any BitLocker recovery keys stored on it.",
1660+
"docsDescription": "Cleans up Entra devices that have not connected/signed in for the specified number of days. Remediation first disables stale enabled devices once they pass the disable threshold, and later deletes devices that are already disabled once they have been inactive for the disable threshold plus the configured grace delta (deletion age = disable threshold + grace days). The disable-before-delete grace period is further guaranteed by never deleting a device in the same pass it was disabled. Hybrid-joined (on-premises synced), Intune-managed/compliant, and system-managed Autopilot devices are excluded, in line with the [Microsoft guidance](https://learn.microsoft.com/en-us/entra/identity/devices/manage-stale-devices). **Warning:** deleting a device permanently removes any BitLocker recovery keys stored on that device object.",
16611661
"executiveText": "Automatically identifies and removes inactive devices that haven't connected to company systems for a specified period, reducing security risks from abandoned or lost devices. This maintains a clean device inventory and prevents potential unauthorized access through dormant device registrations.",
16621662
"addedComponent": [
16631663
{
16641664
"type": "number",
16651665
"name": "standards.StaleEntraDevices.deviceAgeThreshold",
1666-
"label": "Days before stale(Do not set below 30)",
1666+
"required": true,
1667+
"defaultValue": 90,
1668+
"label": "Days before stale (disables the device after this many days of inactivity, minimum 30)",
16671669
"validators": {
16681670
"min": { "value": 30, "message": "Minimum value is 30" }
16691671
}
1672+
},
1673+
{
1674+
"type": "number",
1675+
"name": "standards.StaleEntraDevices.deviceDeleteThreshold",
1676+
"defaultValue": 0,
1677+
"label": "Grace days after disable before deletion (0 = never delete). Devices are deleted once inactive for the disable threshold plus this many additional days.",
1678+
"validators": {
1679+
"min": { "value": 0, "message": "Minimum value is 0" }
1680+
}
16701681
}
16711682
],
1672-
"disabledFeatures": { "report": false, "warn": false, "remediate": true },
1683+
"disabledFeatures": { "report": false, "warn": false, "remediate": false },
16731684
"label": "Cleanup stale Entra devices",
16741685
"impact": "High Impact",
16751686
"impactColour": "danger",
16761687
"addedDate": "2025-01-19",
16771688
"powershellEquivalent": "Remove-MgDevice, Update-MgDevice or Graph API",
16781689
"recommendedBy": [],
1679-
"requiredCapabilities": ["INTUNE_A", "MDM_Services", "EMS", "SCCM", "MICROSOFTINTUNEPLAN1"]
1690+
"requiredCapabilities": []
16801691
},
16811692
{
16821693
"name": "standards.UndoOauth",

Modules/CIPPCore/Public/Get-CIPPIntunePolicy.ps1

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,55 @@ function Get-CIPPIntunePolicy {
429429
return $policies
430430
}
431431
}
432+
'Intents' {
433+
$PlatformType = 'deviceManagement'
434+
$TemplateTypeURL = 'intents'
435+
436+
if ($DisplayName) {
437+
$policies = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/$PlatformType/$TemplateTypeURL" -tenantid $tenantFilter
438+
$policy = $policies | Where-Object -Property displayName -EQ $DisplayName | Sort-Object -Property lastModifiedDateTime -Descending | Select-Object -First 1
439+
if ($policy) {
440+
$settings = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/$PlatformType/$TemplateTypeURL('$($policy.id)')/settings" -tenantid $tenantFilter
441+
$policyDetails = [PSCustomObject]@{
442+
displayName = $policy.displayName
443+
description = $policy.description
444+
templateId = $policy.templateId
445+
settings = @($settings)
446+
}
447+
$policyJson = ConvertTo-Json -InputObject $policyDetails -Depth 100 -Compress
448+
$policy | Add-Member -MemberType NoteProperty -Name 'cippconfiguration' -Value $policyJson -Force
449+
}
450+
return $policy
451+
} elseif ($PolicyId) {
452+
$policy = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/$PlatformType/$TemplateTypeURL('$PolicyId')" -tenantid $tenantFilter
453+
if ($policy) {
454+
$settings = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/$PlatformType/$TemplateTypeURL('$PolicyId')/settings" -tenantid $tenantFilter
455+
$policyDetails = [PSCustomObject]@{
456+
displayName = $policy.displayName
457+
description = $policy.description
458+
templateId = $policy.templateId
459+
settings = @($settings)
460+
}
461+
$policyJson = ConvertTo-Json -InputObject $policyDetails -Depth 100 -Compress
462+
$policy | Add-Member -MemberType NoteProperty -Name 'cippconfiguration' -Value $policyJson -Force
463+
}
464+
return $policy
465+
} else {
466+
$policies = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/$PlatformType/$TemplateTypeURL" -tenantid $tenantFilter
467+
foreach ($policy in $policies) {
468+
$settings = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/$PlatformType/$TemplateTypeURL('$($policy.id)')/settings" -tenantid $tenantFilter
469+
$policyDetails = [PSCustomObject]@{
470+
displayName = $policy.displayName
471+
description = $policy.description
472+
templateId = $policy.templateId
473+
settings = @($settings)
474+
}
475+
$policyJson = ConvertTo-Json -InputObject $policyDetails -Depth 100 -Compress
476+
$policy | Add-Member -MemberType NoteProperty -Name 'cippconfiguration' -Value $policyJson -Force
477+
}
478+
return $policies
479+
}
480+
}
432481
default {
433482
return $null
434483
}

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyMBPerms.ps1

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function Invoke-ExecModifyMBPerms {
1515
# Extract mailbox requests - handle all three formats
1616
$MailboxRequests = $null
1717
$Results = [System.Collections.ArrayList]::new()
18+
$SuccessfulOps = [System.Collections.ArrayList]::new()
1819

1920
# Direct array format
2021
if ($Request.Body -is [array]) {
@@ -150,12 +151,14 @@ function Invoke-ExecModifyMBPerms {
150151
OperationGuid = $OperationGuid # Add GUID to cmdlet object
151152
}
152153

154+
# Use the resolved UPN, not the raw request identifier (which may be an
155+
# object id) - the cache sync below matches cached rows by mailbox UPN.
153156
$CmdletMetadata = [PSCustomObject]@{
154157
ExpectedResult = $Mapping.ExpectedResult
155-
Mailbox = $Username
158+
Mailbox = $UserId
156159
TargetUser = $TargetUser
157160
Permission = $PermissionLevel
158-
Action = $Modification
161+
Action = $Action
159162
OperationGuid = $OperationGuid
160163
}
161164

@@ -202,6 +205,7 @@ function Invoke-ExecModifyMBPerms {
202205
Write-LogMessage -headers $Headers -API $APIName -message "Error for operation $operationGuid`: $ErrorMessage" -Sev 'Error' -tenant $TenantFilter
203206
} else {
204207
$null = $Results.Add($metadata.ExpectedResult)
208+
$null = $SuccessfulOps.Add($metadata)
205209
Write-LogMessage -headers $Headers -API $APIName -message "Success for operation $operationGuid`: $($metadata.ExpectedResult)" -Sev 'Info' -tenant $TenantFilter
206210
}
207211
} else {
@@ -222,6 +226,7 @@ function Invoke-ExecModifyMBPerms {
222226
foreach ($CmdletMetadata in $CmdletMetadataArray) {
223227
if ($CmdletMetadata.ExpectedResult) {
224228
$null = $Results.Add($CmdletMetadata.ExpectedResult)
229+
$null = $SuccessfulOps.Add($CmdletMetadata)
225230
}
226231
}
227232
}
@@ -237,6 +242,7 @@ function Invoke-ExecModifyMBPerms {
237242
try {
238243
$null = New-ExoRequest -Anchor $CmdletMetadata.Mailbox -tenantid $TenantFilter -cmdlet $CmdletObj.CmdletInput.CmdletName -cmdParams $CmdletObj.CmdletInput.Parameters
239244
$null = $Results.Add($CmdletMetadata.ExpectedResult)
245+
$null = $SuccessfulOps.Add($CmdletMetadata)
240246
} catch {
241247
$null = $Results.Add("Error processing $($CmdletMetadata.Permission) for $($CmdletMetadata.TargetUser) on $($CmdletMetadata.Mailbox): $($_.Exception.Message)")
242248
}
@@ -249,13 +255,27 @@ function Invoke-ExecModifyMBPerms {
249255
try {
250256
$null = New-ExoRequest -Anchor $CmdletMetadata.Mailbox -tenantid $TenantFilter -cmdlet $CmdletObj.CmdletInput.CmdletName -cmdParams $CmdletObj.CmdletInput.Parameters
251257
$null = $Results.Add($CmdletMetadata.ExpectedResult)
258+
$null = $SuccessfulOps.Add($CmdletMetadata)
252259
Write-LogMessage -headers $Headers -API $APIName -message "Executed $($CmdletMetadata.Permission) permission modification" -Sev 'Info' -tenant $TenantFilter
253260
} catch {
254261
Write-LogMessage -headers $Headers -API $APIName -message "Permission modification failed: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter
255262
$null = $Results.Add("Error processing $($CmdletMetadata.Permission) for $($CmdletMetadata.TargetUser) on $($CmdletMetadata.Mailbox): $($_.Exception.Message)")
256263
}
257264
}
258265

266+
# Keep the reporting DB cache in step with what actually changed. The bulk path bypasses
267+
# Set-CIPPMailboxPermission's own execute-mode sync (-AsCmdletObject returns early), so
268+
# without this the cached permission report goes stale after every bulk change.
269+
foreach ($Op in $SuccessfulOps) {
270+
if ($Op.Permission -in @('FullAccess', 'SendAs', 'SendOnBehalf')) {
271+
try {
272+
Sync-CIPPMailboxPermissionCache -TenantFilter $TenantFilter -MailboxIdentity $Op.Mailbox -User $Op.TargetUser -PermissionType $Op.Permission -Action $Op.Action
273+
} catch {
274+
Write-Information "Cache sync warning: $($_.Exception.Message)"
275+
}
276+
}
277+
}
278+
259279
$body = [pscustomobject]@{'Results' = @($Results) }
260280
return ([HttpResponseContext]@{
261281
StatusCode = [HttpStatusCode]::OK

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ListmailboxPermissions.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ function Invoke-ListmailboxPermissions {
1414
$UserID = $Request.Query.userId
1515
$UseReportDB = $Request.Query.UseReportDB
1616
$ByUser = $Request.Query.ByUser
17+
$User = $Request.Query.User
1718

1819
try {
1920
# If UseReportDB is specified and no specific UserID, retrieve from report database
@@ -28,6 +29,11 @@ function Invoke-ListmailboxPermissions {
2829
}
2930
try {
3031
$GraphRequest = Get-CIPPMailboxPermissionReport @ReportParams
32+
if ($User -and $ByUser -eq 'true') {
33+
# Only the user-grouped report has a top-level User property; in the
34+
# mailbox-grouped shape this filter would empty the whole result set.
35+
$GraphRequest = @($GraphRequest | Where-Object { $_.User -eq $User })
36+
}
3137
$StatusCode = [HttpStatusCode]::OK
3238
} catch {
3339
$StatusCode = [HttpStatusCode]::InternalServerError

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ExecCompareIntunePolicy.ps1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ function Invoke-ExecCompareIntunePolicy {
2121
'WindowsFeatureUpdateProfiles' = 'windowsFeatureUpdateProfiles'
2222
'windowsQualityUpdatePolicies' = 'windowsQualityUpdatePolicies'
2323
'windowsQualityUpdateProfiles' = 'windowsQualityUpdateProfiles'
24+
'Intents' = 'Intents'
2425
}
2526

2627
try {
@@ -125,6 +126,7 @@ function Invoke-ExecCompareIntunePolicy {
125126
'*configurationPolicies*' { 'Catalog' }
126127
'*managedAppPolicies*' { 'AppProtection' }
127128
'*deviceAppManagement*' { 'AppProtection' }
129+
'*intents*' { 'Intents' }
128130
default { 'Unknown' }
129131
}
130132
$PolicyObj = $ParsedJson

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListIntunePolicy.ps1

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
function Invoke-ListIntunePolicy {
32
<#
43
.FUNCTIONALITY
@@ -216,6 +215,16 @@ function Invoke-ListIntunePolicy {
216215
method = 'GET'
217216
url = "/deviceManagement/configurationPolicies?`$expand=assignments&`$top=1000"
218217
}
218+
@{
219+
id = 'deviceCompliancePolicies'
220+
method = 'GET'
221+
url = "/deviceManagement/deviceCompliancePolicies?`$expand=assignments&`$top=1000"
222+
}
223+
@{
224+
id = 'Intents'
225+
method = 'GET'
226+
url = "/deviceManagement/intents?`$top=1000"
227+
}
219228
)
220229

221230
$BulkResults = New-GraphBulkRequest -Requests $BulkRequests -tenantid $TenantFilter
@@ -226,7 +235,8 @@ function Invoke-ListIntunePolicy {
226235
$GraphRequest = $BulkResults | Where-Object { $_.id -ne 'Groups' } | ForEach-Object {
227236
$URLName = $_.Id
228237
$_.body.Value | ForEach-Object {
229-
$policyTypeName = switch -Wildcard ($_.'assignments@odata.context') {
238+
$AssignmentContext = $_.'assignments@odata.context'
239+
$policyTypeName = switch -Wildcard ($AssignmentContext) {
230240
'*microsoft.graph.windowsIdentityProtectionConfiguration*' { 'Identity Protection' }
231241
'*microsoft.graph.windows10EndpointProtectionConfiguration*' { 'Endpoint Protection' }
232242
'*microsoft.graph.windows10CustomConfiguration*' { 'Custom' }
@@ -244,7 +254,18 @@ function Invoke-ListIntunePolicy {
244254
'*iosUpdateConfiguration*' { 'iOS Update Configuration' }
245255
'*windowsDriverUpdateProfiles*' { 'Driver Update' }
246256
'*configurationPolicies*' { 'Device Configuration' }
247-
default { $_.'assignments@odata.context' }
257+
'*deviceCompliancePolicies*' { 'Compliance Policy' }
258+
'*intents*' { 'Endpoint Security' }
259+
default { $null }
260+
}
261+
# Fall back to the request type when the assignment context does not identify the policy
262+
# (e.g. Intents are listed without expanding assignments).
263+
if ([string]::IsNullOrWhiteSpace($policyTypeName)) {
264+
$policyTypeName = switch ($URLName) {
265+
'deviceCompliancePolicies' { 'Compliance Policy' }
266+
'Intents' { 'Endpoint Security' }
267+
default { $AssignmentContext }
268+
}
248269
}
249270
$Assignments = $_.assignments.target | Select-Object -Property '@odata.type', groupId
250271
$PolicyAssignment = [System.Collections.Generic.List[string]]::new()

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ function Invoke-listStandardTemplates {
3535
} -Force
3636
Write-LogMessage -headers $Request.Headers -API 'Standards' -message "Standards template '$($RowKey)' contained corrupt data (case-duplicate keys) and was automatically repaired and re-saved." -Sev 'Warning'
3737
} catch {
38-
Write-LogMessage -headers $Request.Headers -API 'Standards' -message "Standards template '$($RowKey)' was repaired for this response but could not be re-saved: $($_.Exception.Message)" -Sev 'Warning'
38+
Write-LogMessage -headers $Request.Headers -API 'Standards' -message "Standards template '$($RowKey)' was repaired but could not be re-saved, so it was omitted from the response: $($_.Exception.Message)" -Sev 'Error'
39+
return
3940
}
4041
}
4142
if ($Data) {

0 commit comments

Comments
 (0)