From d37f630ea0c9c525a0fb2794f7a490ec9e4a03c5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 20 Feb 2026 15:01:33 -0500 Subject: [PATCH 01/22] Support missing CIDR prefix and dedupe maxBits calc If the supplied range omits a CIDR prefix (e.g. "10.0.0.0"), default the prefix to the address-family max bits (32 for IPv4, 128 for IPv6). Move the $maxBits calculation before prefix parsing so the default can be applied, and remove the duplicate $maxBits assignment later in the function. This also ensures consistent mask computation for both IPv4 and IPv6. --- Modules/CIPPCore/Public/Authentication/Test-IpInRange.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Authentication/Test-IpInRange.ps1 b/Modules/CIPPCore/Public/Authentication/Test-IpInRange.ps1 index 2279b20ce110..f7103fbca239 100644 --- a/Modules/CIPPCore/Public/Authentication/Test-IpInRange.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Test-IpInRange.ps1 @@ -31,7 +31,8 @@ function Test-IpInRange { $IP = [System.Net.IPAddress]::Parse($IPAddress) $rangeParts = $Range -split '/' $networkAddr = [System.Net.IPAddress]::Parse($rangeParts[0]) - $prefix = [int]$rangeParts[1] + $maxBits = if ($networkAddr.AddressFamily -eq 'InterNetworkV6') { 128 } else { 32 } + $prefix = if ($rangeParts.Count -gt 1) { [int]$rangeParts[1] } else { $maxBits } if ($networkAddr.AddressFamily -ne $IP.AddressFamily) { return $false @@ -39,7 +40,6 @@ function Test-IpInRange { $ipBig = ConvertIpToBigInteger $IP $netBig = ConvertIpToBigInteger $networkAddr - $maxBits = if ($networkAddr.AddressFamily -eq 'InterNetworkV6') { 128 } else { 32 } $shift = $maxBits - $prefix $mask = [System.Numerics.BigInteger]::Pow(2, $shift) - [System.Numerics.BigInteger]::One $invertedMask = [System.Numerics.BigInteger]::MinusOne -bxor $mask From f674c9225bf10cfb910c854af226cd797641b8ac Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 20 Feb 2026 17:32:25 -0500 Subject: [PATCH 02/22] Normalize default tenant groups JSON Update Invoke-ExecCreateDefaultGroups.ps1 to adjust the $DefaultGroups JSON payload. The Business Premium group's DynamicRules were consolidated into a single object with a value array (now including GUIDs for license entries) and several redundant @type fields were simplified for more consistent JSON parsing when creating default tenant groups. --- .../CIPP/Settings/Invoke-ExecCreateDefaultGroups.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCreateDefaultGroups.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCreateDefaultGroups.ps1 index bd2a682429ac..4fc1b2f5575c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCreateDefaultGroups.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCreateDefaultGroups.ps1 @@ -16,7 +16,7 @@ function Invoke-ExecCreateDefaultGroups { $Table = Get-CippTable -tablename 'TenantGroups' $Results = [System.Collections.Generic.List[object]]::new() $ExistingGroups = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'TenantGroup' and Type eq 'dynamic'" - $DefaultGroups = '[{"PartitionKey":"TenantGroup","RowKey":"369d985e-0fba-48f9-844f-9f793b10a12c","Description":"This group does not have a license for intune, nor a license for Entra ID Premium","Description@type":null,"DynamicRules":"[{\"property\":\"availableServicePlan\",\"operator\":\"notIn\",\"value\":[{\"label\":\"Microsoft Intune\",\"value\":\"INTUNE_A\",\"id\":\"c1ec4a95-1f05-45b3-a911-aa3fa01094f5\"}]},{\"property\":\"availableServicePlan\",\"operator\":\"notIn\",\"value\":[{\"label\":\"Microsoft Entra ID P1\",\"value\":\"AAD_PREMIUM\",\"id\":\"41781fb2-bc02-4b7c-bd55-b576c07bb09d\"}]}]","DynamicRules@type":null,"GroupType":"dynamic","GroupType@type":null,"RuleLogic":"and","RuleLogic@type":null,"Name":"Not Intune and Entra Premium Capable","Name@type":null},{"PartitionKey":"TenantGroup","RowKey":"4dbca08b-7dc5-4e0f-bc25-14a90c8e0941","Description":"This group has atleast one Business Premium License available","Description@type":null,"DynamicRules":"[{\"property\":\"availableLicense\",\"operator\":\"in\",\"value\":[{\"label\":\"Microsoft 365 Business Premium\",\"value\":\"SPB\"}]},{\"property\":\"availableLicense\",\"operator\":\"in\",\"value\":[{\"label\":\"Microsoft 365 Business Premium (no Teams)\",\"value\":\"Microsoft_365_ Business_ Premium_(no Teams)\"}]},{\"property\":\"availableLicense\",\"operator\":\"in\",\"value\":[{\"label\":\"Microsoft 365 Business Premium Donation\",\"value\":\"Microsoft_365_Business_Premium_Donation_(Non_Profit_Pricing)\"}]},{\"property\":\"availableLicense\",\"operator\":\"in\",\"value\":[{\"label\":\"Microsoft 365 Business Premium EEA (no Teams)\",\"value\":\"Office_365_w\/o_Teams_Bundle_Business_Premium\"}]}]","DynamicRules@type":null,"GroupType":"dynamic","GroupType@type":null,"RuleLogic":"or","RuleLogic@type":null,"Name":"Business Premium License available","Name@type":null},{"PartitionKey":"TenantGroup","RowKey":"703c0e69-84a8-4dcf-a1c2-4986d2ccc850","Description":"This group does have a license for Entra Premium but does not have a license for Intune","Description@type":null,"DynamicRules":"[{\"property\":\"availableServicePlan\",\"operator\":\"in\",\"value\":[{\"label\":\"Microsoft Entra ID P1\",\"value\":\"AAD_PREMIUM\",\"id\":\"41781fb2-bc02-4b7c-bd55-b576c07bb09d\"}]},{\"property\":\"availableServicePlan\",\"operator\":\"notIn\",\"value\":[{\"label\":\"Microsoft Intune\",\"value\":\"INTUNE_A\",\"id\":\"c1ec4a95-1f05-45b3-a911-aa3fa01094f5\"}]}]","DynamicRules@type":null,"GroupType":"dynamic","GroupType@type":null,"RuleLogic":"and","RuleLogic@type":null,"Name":"Entra Premium Capable, Not Intune Capable","Name@type":null},{"PartitionKey":"TenantGroup","RowKey":"c1dadbc0-f0b4-448c-a2e6-e1938ba102e0","Description":"This group has Intune and Entra ID Premium available","Description@type":null,"DynamicRules":"{\"property\":\"availableServicePlan\",\"operator\":\"in\",\"value\":[{\"label\":\"Microsoft Intune\",\"value\":\"INTUNE_A\"},{\"label\":\"Microsoft Entra ID P1\",\"value\":\"AAD_PREMIUM\"}]}","DynamicRules@type":null,"GroupType":"dynamic","GroupType@type":null,"RuleLogic":"and","RuleLogic@type":null,"Name":"Entra ID Premium and Intune Capable","Name@type":null}]' | ConvertFrom-Json + $DefaultGroups = '[{"PartitionKey":"TenantGroup","RowKey":"369d985e-0fba-48f9-844f-9f793b10a12c","Description":"This group does not have a license for intune, nor a license for Entra ID Premium","Description@type":null,"DynamicRules":"[{\"property\":\"availableServicePlan\",\"operator\":\"notIn\",\"value\":[{\"label\":\"Microsoft Intune\",\"value\":\"INTUNE_A\",\"id\":\"c1ec4a95-1f05-45b3-a911-aa3fa01094f5\"}]},{\"property\":\"availableServicePlan\",\"operator\":\"notIn\",\"value\":[{\"label\":\"Microsoft Entra ID P1\",\"value\":\"AAD_PREMIUM\",\"id\":\"41781fb2-bc02-4b7c-bd55-b576c07bb09d\"}]}]","DynamicRules@type":null,"GroupType":"dynamic","GroupType@type":null,"RuleLogic":"and","RuleLogic@type":null,"Name":"Not Intune and Entra Premium Capable","Name@type":null},{"PartitionKey":"TenantGroup","RowKey":"4dbca08b-7dc5-4e0f-bc25-14a90c8e0941","Description":"This group has atleast one Business Premium License available","DynamicRules":"{\"property\":\"availableLicense\",\"operator\":\"in\",\"value\":[{\"label\":\"Microsoft 365 Business Premium\",\"value\":\"SPB\",\"guid\":\"cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46\"},{\"label\":\"Microsoft 365 Business Premium (no Teams)\",\"value\":\"Microsoft_365_ Business_ Premium_(no Teams)\",\"guid\":\"00e1ec7b-e4a3-40d1-9441-b69b597ab222\"},{\"label\":\"Microsoft 365 Business Premium Donation\",\"value\":\"Microsoft_365_Business_Premium_Donation_(Non_Profit_Pricing)\",\"guid\":\"24c35284-d768-4e53-84d9-b7ae73dddf69\"},{\"label\":\"Microsoft 365 Business Premium EEA (no Teams)\",\"value\":\"Office_365_w/o_Teams_Bundle_Business_Premium\",\"guid\":\"a3f586b6-8cce-4d9b-99d6-55238397f77a\"}]}","GroupType":"dynamic","Name":"Business Premium License available","RuleLogic":"or"},{"PartitionKey":"TenantGroup","RowKey":"703c0e69-84a8-4dcf-a1c2-4986d2ccc850","Description":"This group does have a license for Entra Premium but does not have a license for Intune","Description@type":null,"DynamicRules":"[{\"property\":\"availableServicePlan\",\"operator\":\"in\",\"value\":[{\"label\":\"Microsoft Entra ID P1\",\"value\":\"AAD_PREMIUM\",\"id\":\"41781fb2-bc02-4b7c-bd55-b576c07bb09d\"}]},{\"property\":\"availableServicePlan\",\"operator\":\"notIn\",\"value\":[{\"label\":\"Microsoft Intune\",\"value\":\"INTUNE_A\",\"id\":\"c1ec4a95-1f05-45b3-a911-aa3fa01094f5\"}]}]","DynamicRules@type":null,"GroupType":"dynamic","GroupType@type":null,"RuleLogic":"and","RuleLogic@type":null,"Name":"Entra Premium Capable, Not Intune Capable","Name@type":null},{"PartitionKey":"TenantGroup","RowKey":"c1dadbc0-f0b4-448c-a2e6-e1938ba102e0","Description":"This group has Intune and Entra ID Premium available","Description@type":null,"DynamicRules":"{\"property\":\"availableServicePlan\",\"operator\":\"in\",\"value\":[{\"label\":\"Microsoft Intune\",\"value\":\"INTUNE_A\"},{\"label\":\"Microsoft Entra ID P1\",\"value\":\"AAD_PREMIUM\"}]}","DynamicRules@type":null,"GroupType":"dynamic","GroupType@type":null,"RuleLogic":"and","RuleLogic@type":null,"Name":"Entra ID Premium and Intune Capable","Name@type":null}]' | ConvertFrom-Json foreach ($Group in $DefaultGroups) { From b6e89c2a551895c2f6d2463204db54487c37ef47 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 20 Feb 2026 18:14:01 -0500 Subject: [PATCH 03/22] Add log entry to Invoke-AddAlert Add a Write-LogMessage call to Invoke-AddAlert to record alert additions (API='AddAlert') with message, severity Info, LogData and request headers for telemetry/troubleshooting. Also normalize the function keyword casing from 'Function' to 'function' for consistency. --- .../Tenant/Administration/Alerts/Invoke-AddAlert.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-AddAlert.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-AddAlert.ps1 index a683b153c508..df6818654545 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-AddAlert.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-AddAlert.ps1 @@ -1,4 +1,4 @@ -Function Invoke-AddAlert { +function Invoke-AddAlert { <# .FUNCTIONALITY Entrypoint @@ -27,6 +27,7 @@ Function Invoke-AddAlert { $WebhookTable = Get-CippTable -TableName 'WebhookRules' Add-CIPPAzDataTableEntity @WebhookTable -Entity $CompleteObject -Force $Results = "Added Audit Log Alert for $($Tenants.count) tenants. It may take up to four hours before Microsoft starts delivering these alerts." + Write-LogMessage -API 'AddAlert' -message $Results -sev Info -LogData $CompleteObject -headers $Request.Headers return ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK From 5f240c15f99694725f84223fb895bf9ba9f43479 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 20 Feb 2026 18:23:21 -0500 Subject: [PATCH 04/22] fix quarantine return --- .../Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 index 58ed3b035407..b20096d59020 100644 --- a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 +++ b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 @@ -14,7 +14,7 @@ #Add rerun protection: This Monitor can only run once every hour. $Rerun = Test-CIPPRerun -TenantFilter $TenantFilter -Type 'ExchangeMonitor' -API 'Get-CIPPAlertQuarantineReleaseRequests' if ($Rerun) { - return $true + return } $HasLicense = Test-CIPPStandardLicense -StandardName 'QuarantineReleaseRequests' -TenantFilter $TenantFilter -RequiredCapabilities @( 'EXCHANGE_S_STANDARD', @@ -25,7 +25,7 @@ ) if (-not $HasLicense) { - return $true + return } try { From e9a01a9c4ecd0e8b57f276b2d19292069a763f21 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 20 Feb 2026 19:31:11 -0500 Subject: [PATCH 05/22] Add cleanup rule and use OData timestamp filters Invoke server-side OData timestamp filtering and add a table cleanup rule for quarantine messages. - Invoke-ListMailQuarantine.ps1: replace client-side Where-Object Timestamp check with an OData filter that uses a UTC datetime string (yyyy-MM-ddTHH:mm:ssZ). This constructs the timestamp ($30MinutesAgo) via ToUniversalTime and embeds it in the Table query to avoid fetching then filtering locally. - Start-TableCleanup.ps1: add a CleanupRule entry for the cacheQuarantineMessages table to delete QuarantineMessage rows older than 1 day (uses an OData lt datetime filter). The rule requests up to 10000 rows and returns PartitionKey/RowKey/ETag for deletion. These changes move time-based filtering into the Azure Table query to reduce data transfer and add automated cleanup for quarantine messages. --- .../Spamfilter/Invoke-ListMailQuarantine.ps1 | 5 +++-- .../Entrypoints/Timer Functions/Start-TableCleanup.ps1 | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-ListMailQuarantine.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-ListMailQuarantine.ps1 index d785b015f940..7780f90588eb 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-ListMailQuarantine.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-ListMailQuarantine.ps1 @@ -16,8 +16,9 @@ function Invoke-ListMailQuarantine { } else { $Table = Get-CIPPTable -TableName cacheQuarantineMessages $PartitionKey = 'QuarantineMessage' - $Filter = "PartitionKey eq '$PartitionKey'" - $Rows = Get-CIPPAzDataTableEntity @Table -filter $Filter | Where-Object -Property Timestamp -GT (Get-Date).AddMinutes(-30) + $30MinutesAgo = (Get-Date).AddMinutes(-30).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + $Filter = "PartitionKey eq '$PartitionKey' and Timestamp gt datetime'$30MinutesAgo'" + $Rows = Get-CIPPAzDataTableEntity @Table -filter $Filter $QueueReference = '{0}-{1}' -f $TenantFilter, $PartitionKey $RunningQueue = Invoke-ListCippQueue -Reference $QueueReference | Where-Object { $_.Status -notmatch 'Completed' -and $_.Status -notmatch 'Failed' } # If a queue is running, we will not start a new one diff --git a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-TableCleanup.ps1 b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-TableCleanup.ps1 index 74f620e133e1..771e9d66363c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-TableCleanup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-TableCleanup.ps1 @@ -75,6 +75,16 @@ function Start-TableCleanup { Property = @('PartitionKey', 'RowKey', 'ETag') } } + @{ + FunctionName = 'TableCleanupTask' + Type = 'CleanupRule' + TableName = 'cacheQuarantineMessages' + DataTableProps = @{ + Filter = "PartitionKey eq 'QuarantineMessage' and Timestamp lt datetime'$((Get-Date).AddDays(-1).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ'))'" + First = 10000 + Property = @('PartitionKey', 'RowKey', 'ETag') + } + } @{ FunctionName = 'TableCleanupTask' Type = 'DeleteTable' From 80c4477632674cff5a02259bf3d82343dc1dac26 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 20 Feb 2026 22:12:41 -0500 Subject: [PATCH 06/22] cleanup logging --- .../Activity Triggers/Push-ExecScheduledCommand.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 index 22607cb0841b..a7744b0a1af0 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 @@ -317,13 +317,12 @@ function Push-ExecScheduledCommand { } Write-LogMessage -API 'Scheduler_UserTasks' -tenant $Tenant -tenantid $TenantInfo.customerId -message "Failed to execute task $($task.Name): $errorMessage" -sev Error -LogData (Get-CippExceptionData -Exception $_.Exception) } - Write-Information 'Sending task results to target. Updating the task state.' # For orchestrator-based commands, skip post-execution alerts as they will be handled by the orchestrator's post-execution function if ($Results -and $Item.Command -notin $OrchestratorBasedCommands) { + Write-Information "Sending task results to post execution target(s): $($Task.PostExecution -join ', ')." Send-CIPPScheduledTaskAlert -Results $Results -TaskInfo $task -TenantFilter $Tenant -TaskType $TaskType } - Write-Information 'Sent the results to the target. Updating the task state.' try { # For orchestrator-based commands, skip task state update as it will be handled by post-execution From 68e8562bd0e045e7e597df17dc2dd699a9bc5f16 Mon Sep 17 00:00:00 2001 From: Chris Dewey <142454021+chris-dewey-1991@users.noreply.github.com> Date: Sun, 22 Feb 2026 21:36:56 +0000 Subject: [PATCH 07/22] Fix drift comparison issue for NotifyOutboundSpamRecipients Convert NotifyOutboundSpamRecipients array to comma-separated string to fix drift comparison and reporting issues --- .../Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 index 0ae5d1e92eab..d703bf2c5569 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 @@ -76,7 +76,7 @@ function Invoke-CIPPStandardOutBoundSpamAlert { Add-CIPPBPAField -FieldName 'OutboundSpamAlert' -FieldValue $CurrentInfo.NotifyOutboundSpam -StoreAs bool -Tenant $tenant $CurrentValue = @{ NotifyOutboundSpam = $CurrentInfo.NotifyOutboundSpam - NotifyOutboundSpamRecipients = $CurrentInfo.NotifyOutboundSpamRecipients + NotifyOutboundSpamRecipients = ($CurrentInfo.NotifyOutboundSpamRecipients -join ', ') } $ExpectedValue = @{ NotifyOutboundSpam = $true From d06acf015b36eb0f9860df6517e75d5298a1df61 Mon Sep 17 00:00:00 2001 From: Chris Dewey <142454021+chris-dewey-1991@users.noreply.github.com> Date: Sun, 22 Feb 2026 21:43:56 +0000 Subject: [PATCH 08/22] Fix self-service license handling and logging Refactor logic to avoid mutating original objects and improve logging. --- ...CIPPStandardDisableSelfServiceLicenses.ps1 | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 index 03845d7a4d03..1a5bad508ffb 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 @@ -45,41 +45,54 @@ function Invoke-CIPPStandardDisableSelfServiceLicenses { throw $Message } - if ($settings.exclusions -like '*;*') { $exclusions = $settings.Exclusions -split (';') } else { $exclusions = $settings.Exclusions -split (',') } + $CurrentValues = $selfServiceItems | Select-Object -Property productName, productId, policyValue + $ExpectedValues = [System.Collections.Generic.List[PSCustomObject]]::new() + foreach ($Item in $selfServiceItems) { + if ($Item.productId -in $exclusions) { - $Item.policyValue = "Enabled" - $ExpectedValues.add(($Item | Select-Object -Property productName, productId, policyValue)) - Write-LogMessage -API 'Standards' -tenant $Tenant -message "Exclusion present for self-service license '$($Item.productName) - $($Item.productId)'" + $desiredPolicyValue = "Enabled" + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Exclusion present for self-service license '$($Item.productName) - $($Item.productId)'" } else { - $Item.policyValue = "Disabled" - $ExpectedValues.add(($Item | Select-Object -Property productName, productId, policyValue)) + $desiredPolicyValue = "Disabled" } - } - $CurrentValues = $selfServiceItems | Select-Object -Property productName, productId, policyValue + $ExpectedValues.Add([PSCustomObject]@{ + productName = $Item.productName + productId = $Item.productId + policyValue = $desiredPolicyValue + }) + } if ($settings.remediate) { + $Compare = Compare-Object -ReferenceObject $ExpectedValues -DifferenceObject $CurrentValues -Property productName, productId, policyValue if (!$Compare) { Write-LogMessage -API 'Standards' -tenant $Tenant -message 'self service licenses are already set correctly.' -sev Info } else { - $NeedsUpdate = $Compare | Where-Object {$_.SideIndicator -eq "<="} + + $NeedsUpdate = $Compare | Where-Object { $_.SideIndicator -eq "<=" } + foreach ($Item in $NeedsUpdate) { try { - $body = @{policyValue=$Item.policyValue} | ConvertTo-Json -Compress + + $currentItem = $CurrentValues | Where-Object { $_.productId -eq $Item.productId } | Select-Object -First 1 + $currentValue = if ($currentItem) { $currentItem.policyValue } else { "" } + + $body = @{ policyValue = $Item.policyValue } | ConvertTo-Json -Compress New-GraphPOSTRequest -scope 'aeb86249-8ea3-49e2-900b-54cc8e308f85/.default' -uri "https://licensing.m365.microsoft.com/v1.0/policies/AllowSelfServicePurchase/products/$($Item.productId)" -tenantid $Tenant -body $body -type PUT - Write-LogMessage -API 'Standards' -tenant $tenant -message "Changed Self Service status for product '$($Item.productName) - $($Item.productId)' to '$($Item.policyValue)'" + + Write-LogMessage -API 'Standards' -tenant $tenant -message "Changed Self Service status for product '$($Item.productName) - $($Item.productId)' from '$currentValue' to '$($Item.policyValue)'" -sev Info } catch { Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set product status for '$($Item.productName) - $($Item.productId)' with body $($body) for reason: $($_.Exception.Message)" -sev Error } @@ -100,12 +113,13 @@ function Invoke-CIPPStandardDisableSelfServiceLicenses { } if ($Settings.report -eq $true) { + $StateIsCorrect = !(Compare-Object -ReferenceObject $ExpectedValues -DifferenceObject $CurrentValues -Property productName, productId, policyValue) $ExpectedValuesHash = @{} foreach ($Item in $ExpectedValues) { $ExpectedValuesHash[$Item.productName] = [PSCustomObject]@{ - Id = $Item.productId + Id = $Item.productId Value = $Item.policyValue } } @@ -114,7 +128,7 @@ function Invoke-CIPPStandardDisableSelfServiceLicenses { $CurrentValuesHash = @{} foreach ($Item in $CurrentValues) { $CurrentValuesHash[$Item.productName] = [PSCustomObject]@{ - Id = $Item.productId + Id = $Item.productId Value = $Item.policyValue } } From 518d970fa3e31726ea2b30425c6fd62254668566 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 22 Feb 2026 21:42:15 -0500 Subject: [PATCH 09/22] Use Graph bulk API and improve group handling Replace multiple per-call Graph requests with a single New-GraphBulkRequest to improve performance and reduce API calls. Adds a BulkInfoRequests list to fetch user ID (when missing), all groups, and the user's memberOf groups in one bulk call. Also switches from a GetMemberGroups POST to the memberOf/microsoft.graph.group GET result and adjusts downstream logic for mail-enabled, M365, licensed, and dynamic groups. This avoids returning transitive group memberships. --- Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 b/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 index 527de771990e..f901ac82953e 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 @@ -8,14 +8,38 @@ function Remove-CIPPGroups { $UserID ) - if (-not $userid) { - $UserID = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($Username)" -tenantid $TenantFilter).id + $BulkInfoRequests = [System.Collections.Generic.List[object]]::new() + + if (-not $UserID) { + $BulkInfoRequests.Add(@{ + id = 'getUserID' + method = 'GET' + url = "users/$($Username)?`$select=id" + }) } - $AllGroups = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/?`$select=displayName,mailEnabled,id,groupTypes,assignedLicenses,onPremisesSyncEnabled,membershipRule&`$top=999" -tenantid $TenantFilter) - # Get user's groups - $UserGroups = (New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($UserID)/GetMemberGroups" -tenantid $TenantFilter -type POST -body '{"securityEnabledOnly": false}').value + $BulkInfoRequests.Add( + @{ + id = 'getAllGroups' + method = 'GET' + url = "groups/?`$select=displayName,mailEnabled,id,groupTypes,assignedLicenses,onPremisesSyncEnabled,membershipRule&`$top=999" + }) + $BulkInfoRequests.Add(@{ + id = 'getUserGroups' + method = 'GET' + url = "users/$($UserID ?? $Username)/memberOf/microsoft.graph.group?`$select=id" + }) + + $BulkGetResults = New-GraphBulkRequest -tenantid $TenantFilter -Requests @($BulkInfoRequests) + + $UserInfo = ($BulkGetResults | Where-Object { $_.id -eq 'getUserID' }).body + if ($UserInfo) { + $UserID = $UserInfo.id + } + $AllGroups = ($BulkGetResults | Where-Object { $_.id -eq 'getAllGroups' }).body.value + $UserGroups = ($BulkGetResults | Where-Object { $_.id -eq 'getUserGroups' }).body.value + #users/$($User.id)/memberOf/microsoft.graph.directoryRole if (-not $UserGroups) { $Returnval = "$($Username) is not a member of any groups." Write-LogMessage -headers $Headers -API $APIName -message "$($Username) is not a member of any groups" -Sev 'Info' -tenant $TenantFilter @@ -31,10 +55,10 @@ function Remove-CIPPGroups { # Process each group and prepare bulk requests foreach ($Group in $UserGroups) { - $GroupInfo = $AllGroups | Where-Object -Property id -EQ $Group + $GroupInfo = $AllGroups | Where-Object -Property id -EQ $Group.id $GroupName = $GroupInfo.displayName $IsMailEnabled = $GroupInfo.mailEnabled - $IsM365Group = $null -ne ($AllGroups | Where-Object { $_.id -eq $Group -and $_.groupTypes -contains 'Unified' }) + $IsM365Group = $GroupInfo.groupTypes -and $GroupInfo.groupTypes -contains 'Unified' $IsLicensed = $GroupInfo.assignedLicenses.Count -gt 0 $IsDynamic = -not [string]::IsNullOrWhiteSpace($GroupInfo.membershipRule) @@ -51,13 +75,13 @@ function Remove-CIPPGroups { if ($IsM365Group -or (-not $IsMailEnabled)) { # Use Graph API for M365 Groups and Security Groups $BulkRequests.Add(@{ - id = "removeFromGroup-$Group" + id = "removeFromGroup-$($Group.id)" method = 'DELETE' - url = "groups/$Group/members/$UserID/`$ref" + url = "groups/$($Group.id)/members/$UserID/`$ref" }) $GraphLogs.Add(@{ message = "Removed $Username from $GroupName" - id = "removeFromGroup-$Group" + id = "removeFromGroup-$($Group.id)" groupName = $GroupName }) } elseif ($IsMailEnabled) { From aa885f179f6c665ea0eedc27a9ef65e613b79cd5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 22 Feb 2026 23:46:48 -0500 Subject: [PATCH 10/22] Standardize log severity and propagate headers Normalize logging across the codebase by replacing Write-LogMessage calls using -Sev 'Warning' with -sev 'Warn' and standardizing the severity parameter name. Propagate Headers through the offboarding orchestration (Invoke-CIPPOffboardingJob, Push-CIPPOffboardingComplete and related task parameter objects), include headers in log calls and post-execution parameters, and attach error log data where applicable. Also restrict Get-Command lookup to the CIPPCore module in Push-CIPPOffboardingTask and apply minor whitespace/formatting fixes. --- .../Public/Add-CIPPWin32LobAppContent.ps1 | 2 +- .../Push-CIPPOffboardingComplete.ps1 | 7 +- .../Push-CIPPOffboardingTask.ps1 | 2 +- .../Invoke-ListScheduledItemDetails.ps1 | 4 +- .../CIPP/Settings/Invoke-ExecApiClient.ps1 | 2 +- .../CIPP/Settings/Invoke-ExecGDAPTrace.ps1 | 20 +- .../Settings/Invoke-ListCustomVariables.ps1 | 2 +- .../Invoke-DeployContactTemplates.ps1 | 6 +- .../Contacts/Invoke-ListContactTemplates.ps1 | 2 +- .../Administration/Invoke-ExecHVEUser.ps1 | 4 +- .../Invoke-ExecModifyCalPerms.ps1 | 2 +- .../Invoke-ExecModifyContactPerms.ps1 | 2 +- .../Invoke-ExecModifyMBPerms.ps1 | 4 +- .../Users/Invoke-AddJITAdminTemplate.ps1 | 4 +- .../Users/Invoke-CIPPOffboardingJob.ps1 | 43 ++-- .../Users/Invoke-EditJITAdminTemplate.ps1 | 4 +- .../Users/Invoke-ListJITAdminTemplates.ps1 | 2 +- .../Users/Invoke-RemoveJITAdminTemplate.ps1 | 4 +- .../Invoke-RemoveUserDefaultTemplate.ps1 | 2 +- .../Invoke-AddSafeLinksPolicyFromTemplate.ps1 | 4 +- .../Invoke-ExecDeleteSafeLinksPolicy.ps1 | 4 +- .../Invoke-ExecNewSafeLinksPolicy.ps1 | 4 +- .../Invoke-ListSafeLinksPolicy.ps1 | 2 +- .../Invoke-ListSafeLinksPolicyDetails.ps1 | 4 +- .../Invoke-ExecCreateAppTemplate.ps1 | 12 +- .../Tenant/Invoke-ListTenants.ps1 | 2 +- .../GDAP/Invoke-ExecGDAPRoleTemplate.ps1 | 8 +- .../Invoke-ExecUpdateDriftDeviation.ps1 | 2 +- .../Entrypoints/Invoke-ExecListAppId.ps1 | 4 +- .../Public/Entrypoints/Invoke-ListLogs.ps1 | 4 +- .../Start-BackupRetentionCleanup.ps1 | 4 +- .../CIPPCore/Public/New-CIPPRestoreTask.ps1 | 2 +- .../Public/Remove-CIPPCalendarPermissions.ps1 | 6 +- Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 | 186 +++++++++--------- .../Public/Remove-CIPPMailboxPermissions.ps1 | 2 +- .../Public/Remove-CIPPMailboxRule.ps1 | 2 +- .../Public/Set-CIPPAssignedPolicy.ps1 | 4 +- .../CIPPCore/Public/Set-CIPPMailboxRule.ps1 | 2 +- .../CIPPCore/Public/Set-CIPPResetPassword.ps1 | 2 +- .../CIPPCore/Public/Test-CIPPAccessTenant.ps1 | 2 +- .../Invoke-CIPPGraphWebhookRenewal.ps1 | 2 +- .../Public/PwPush/New-PwPushLink.ps1 | 2 +- 42 files changed, 207 insertions(+), 177 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPWin32LobAppContent.ps1 b/Modules/CIPPCore/Public/Add-CIPPWin32LobAppContent.ps1 index 1b238870e622..2b9b7393d9c4 100644 --- a/Modules/CIPPCore/Public/Add-CIPPWin32LobAppContent.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPWin32LobAppContent.ps1 @@ -138,7 +138,7 @@ function Add-CIPPWin32LobAppContent { if ($CommitStateReq.uploadState -like '*fail*') { $errorMsg = "Commit failed. Upload state: $($CommitStateReq.uploadState)" if ($Headers) { - Write-LogMessage -Headers $Headers -API $APIName -message $errorMsg -Sev 'Warning' -tenant $TenantFilter + Write-LogMessage -Headers $Headers -API $APIName -message $errorMsg -sev 'Warn' -tenant $TenantFilter } throw $errorMsg } diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPOffboardingComplete.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPOffboardingComplete.ps1 index 8d86e31a27a3..faca0853ea6f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPOffboardingComplete.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPOffboardingComplete.ps1 @@ -15,6 +15,7 @@ function Push-CIPPOffboardingComplete { $TaskInfo = $Item.Parameters.TaskInfo $TenantFilter = $Item.Parameters.TenantFilter $Username = $Item.Parameters.Username + $Headers = $Item.Parameters.Headers $Results = $Item.Results # Results come from orchestrator, not Parameters try { @@ -102,19 +103,19 @@ function Push-CIPPOffboardingComplete { TaskState = 'Completed' } - Write-LogMessage -API 'Offboarding' -tenant $TenantFilter -message "Offboarding completed successfully for $Username" -sev Info + Write-LogMessage -API 'Offboarding' -tenant $TenantFilter -message "Offboarding completed successfully for $Username" -sev Info -headers $Headers # Send post-execution alerts if configured if ($TaskInfo.PostExecution -and $ProcessedResults) { Send-CIPPScheduledTaskAlert -Results $ProcessedResults -TaskInfo $TaskInfo -TenantFilter $TenantFilter } } - + Write-LogMessage -API 'Offboarding' -tenant $TenantFilter -message "Offboarding completed for $Username" -sev Info -headers $Headers return "Offboarding completed for $Username" } catch { $ErrorMsg = "Failed to complete offboarding for $Username : $($_.Exception.Message)" - Write-LogMessage -API 'Offboarding' -tenant $TenantFilter -message $ErrorMsg -sev Error + Write-LogMessage -API 'Offboarding' -tenant $TenantFilter -message $ErrorMsg -sev Error -headers $Headers -LogData (Get-CippException -Exception $_) throw $ErrorMsg } } diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPOffboardingTask.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPOffboardingTask.ps1 index 7f0243f4d9c9..eacb976bf70e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPOffboardingTask.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPOffboardingTask.ps1 @@ -19,7 +19,7 @@ function Push-CIPPOffboardingTask { Write-Information "Executing offboarding cmdlet: $Cmdlet" # Check if cmdlet exists - $CmdletInfo = Get-Command -Name $Cmdlet -ErrorAction SilentlyContinue + $CmdletInfo = Get-Command -Name $Cmdlet -Module CIPPCore -ErrorAction SilentlyContinue if (-not $CmdletInfo) { throw "Cmdlet $Cmdlet does not exist" } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 index be60e04ec9b2..b5d04c9de337 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 @@ -119,7 +119,7 @@ function Invoke-ListScheduledItemDetails { } } catch { # If JSON parsing fails, use raw value - Write-LogMessage -API $APIName -message "Error parsing Task.Results as JSON: $_" -Sev 'Warning' + Write-LogMessage -API $APIName -message "Error parsing Task.Results as JSON: $_" -sev 'Warn' $ResultData = $Task.Results } } else { @@ -155,7 +155,7 @@ function Invoke-ListScheduledItemDetails { try { $ParsedResults = $Result.Results | ConvertFrom-Json -ErrorAction Stop } catch { - Write-LogMessage -API $APIName -message "Failed to parse result as JSON: $_" -Sev 'Warning' + Write-LogMessage -API $APIName -message "Failed to parse result as JSON: $_" -sev 'Warn' # On failure, keep as string $ParsedResults = $Result.Results } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecApiClient.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecApiClient.ps1 index c38ece6e144c..eb7dc7273680 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecApiClient.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecApiClient.ps1 @@ -192,7 +192,7 @@ function Invoke-ExecApiClient { $Body = @{ Results = "API client $ClientId not found or not a valid CIPP-API application" } } } catch { - Write-LogMessage -headers $Request.Headers -API 'ExecApiClient' -message "Failed to remove app registration for $ClientId" -Sev 'Warning' + Write-LogMessage -headers $Request.Headers -API 'ExecApiClient' -message "Failed to remove app registration for $ClientId" -sev 'Warn' } } default { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecGDAPTrace.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecGDAPTrace.ps1 index d138555690a5..930c54b297d9 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecGDAPTrace.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecGDAPTrace.ps1 @@ -163,7 +163,7 @@ function Invoke-ExecAccessTest { # Filter didn't work, try direct lookup by UPN (works if UPN is unique identifier) $User = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$UPN" -tenantid $env:TenantID -NoAuthCheck $true } catch { - Write-LogMessage -Headers $Headers -API $APIName -message "Could not find user $UPN in partner tenant: $($_.Exception.Message)" -Sev 'Warning' + Write-LogMessage -Headers $Headers -API $APIName -message "Could not find user $UPN in partner tenant: $($_.Exception.Message)" -sev 'Warn' } # If user not found, return error @@ -212,7 +212,7 @@ function Invoke-ExecAccessTest { } } } catch { - Write-LogMessage -Headers $Headers -API $APIName -message "Could not get user group memberships: $($_.Exception.Message)" -Sev 'Warning' + Write-LogMessage -Headers $Headers -API $APIName -message "Could not get user group memberships: $($_.Exception.Message)" -sev 'Warn' } # ============================================================================ @@ -296,7 +296,7 @@ function Invoke-ExecAccessTest { }) } } catch { - Write-LogMessage -Headers $Headers -API $APIName -message "Could not get access assignments for relationship ${RelationshipName}: $($_.Exception.Message)" -Sev 'Warning' + Write-LogMessage -Headers $Headers -API $APIName -message "Could not get access assignments for relationship ${RelationshipName}: $($_.Exception.Message)" -sev 'Warn' } } @@ -346,7 +346,7 @@ function Invoke-ExecAccessTest { Write-LogMessage -Headers $Headers -API $APIName -message "Fetched $($AllGroups.Count) total groups, $($GroupLookup.Count) in lookup" -Sev 'Debug' } catch { - Write-LogMessage -Headers $Headers -API $APIName -message "Could not fetch all groups: $($_.Exception.Message). Will use fallback for missing groups." -Sev 'Warning' + Write-LogMessage -Headers $Headers -API $APIName -message "Could not fetch all groups: $($_.Exception.Message). Will use fallback for missing groups." -sev 'Warn' } # ======================================================================== @@ -387,12 +387,12 @@ function Invoke-ExecAccessTest { $GroupId = $Assignment.value.accessContainer.accessContainerId $Assignment = $Assignment.value } else { - Write-LogMessage -Headers $Headers -API $APIName -message "Access assignment missing accessContainer: $($Assignment | ConvertTo-Json -Compress)" -Sev 'Warning' + Write-LogMessage -Headers $Headers -API $APIName -message "Access assignment missing accessContainer: $($Assignment | ConvertTo-Json -Compress)" -sev 'Warn' continue } if ([string]::IsNullOrWhiteSpace($GroupId)) { - Write-LogMessage -Headers $Headers -API $APIName -message "Access assignment has empty accessContainerId: $($Assignment | ConvertTo-Json -Compress)" -Sev 'Warning' + Write-LogMessage -Headers $Headers -API $APIName -message "Access assignment has empty accessContainerId: $($Assignment | ConvertTo-Json -Compress)" -sev 'Warn' continue } @@ -405,7 +405,7 @@ function Invoke-ExecAccessTest { } if (-not $Roles -or $Roles.Count -eq 0) { - Write-LogMessage -Headers $Headers -API $APIName -message "Access assignment for group $GroupId has no roles assigned" -Sev 'Warning' + Write-LogMessage -Headers $Headers -API $APIName -message "Access assignment for group $GroupId has no roles assigned" -sev 'Warn' $Roles = @() } @@ -420,7 +420,7 @@ function Invoke-ExecAccessTest { id = $GroupId displayName = "Unknown Group ($GroupId)" } - Write-LogMessage -Headers $Headers -API $APIName -message "Group $GroupId not found in lookup, using fallback" -Sev 'Warning' + Write-LogMessage -Headers $Headers -API $APIName -message "Group $GroupId not found in lookup, using fallback" -sev 'Warn' } # Process the assignment even if group lookup failed - we still have the group ID and roles @@ -585,12 +585,12 @@ function Invoke-ExecAccessTest { } elseif ($Role -is [string]) { $RoleId = $Role } else { - Write-LogMessage -Headers $Headers -API $APIName -message "Role object missing roleDefinitionId: $($Role | ConvertTo-Json -Compress)" -Sev 'Warning' + Write-LogMessage -Headers $Headers -API $APIName -message "Role object missing roleDefinitionId: $($Role | ConvertTo-Json -Compress)" -sev 'Warn' continue } if ([string]::IsNullOrWhiteSpace($RoleId)) { - Write-LogMessage -Headers $Headers -API $APIName -message "Role has empty roleDefinitionId for group $GroupId" -Sev 'Warning' + Write-LogMessage -Headers $Headers -API $APIName -message "Role has empty roleDefinitionId for group $GroupId" -sev 'Warn' continue } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCustomVariables.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCustomVariables.ps1 index d512c090da28..e77018c09ffc 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCustomVariables.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCustomVariables.ps1 @@ -242,7 +242,7 @@ function Invoke-ListCustomVariables { } } } catch { - Write-LogMessage -API $APIName -message "Could not retrieve tenant-specific variables for $TenantFilter : $($_.Exception.Message)" -Sev 'Warning' + Write-LogMessage -API $APIName -message "Could not retrieve tenant-specific variables for $TenantFilter : $($_.Exception.Message)" -sev 'Warn' } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Contacts/Invoke-DeployContactTemplates.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Contacts/Invoke-DeployContactTemplates.ps1 index 84b79cff13be..d3a188c2211c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Contacts/Invoke-DeployContactTemplates.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Contacts/Invoke-DeployContactTemplates.ps1 @@ -24,7 +24,7 @@ Function Invoke-DeployContactTemplates { if ($TenantItem.value) { $SelectedTenants.Add($TenantItem.value) } else { - Write-LogMessage -headers $Headers -API $APIName -message "Tenant item missing value property: $($TenantItem | ConvertTo-Json -Compress)" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message "Tenant item missing value property: $($TenantItem | ConvertTo-Json -Compress)" -sev 'Warn' } } @@ -46,7 +46,7 @@ Function Invoke-DeployContactTemplates { if ($TemplateItem.value) { $ContactTemplates.Add($TemplateItem.value) } else { - Write-LogMessage -headers $Headers -API $APIName -message "Template item missing value property: $($TemplateItem | ConvertTo-Json -Compress)" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message "Template item missing value property: $($TemplateItem | ConvertTo-Json -Compress)" -sev 'Warn' } } } else { @@ -74,7 +74,7 @@ Function Invoke-DeployContactTemplates { $ContactExists = $ExistingContacts | Where-Object { $_.ExternalEmailAddress -eq $ContactTemplate.email } if ($ContactExists) { - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Contact with email '$($ContactTemplate.email)' already exists in tenant $TenantFilter" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Contact with email '$($ContactTemplate.email)' already exists in tenant $TenantFilter" -sev 'Warn' "Contact '$($ContactTemplate.displayName)' with email '$($ContactTemplate.email)' already exists in tenant $TenantFilter" continue } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Contacts/Invoke-ListContactTemplates.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Contacts/Invoke-ListContactTemplates.ps1 index 9abe8a741a25..05ae42c1ed52 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Contacts/Invoke-ListContactTemplates.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Contacts/Invoke-ListContactTemplates.ps1 @@ -37,7 +37,7 @@ Function Invoke-ListContactTemplates { } if (-not $Templates) { - Write-LogMessage -headers $Headers -API $APIName -message "Template with ID $RequestedID not found" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message "Template with ID $RequestedID not found" -sev 'Warn' return ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::NotFound Body = @{ Error = "Template with ID $RequestedID not found" } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecHVEUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecHVEUser.ps1 index bdb783995d9b..5c1b8e2e52d0 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecHVEUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecHVEUser.ps1 @@ -75,7 +75,7 @@ function Invoke-ExecHVEUser { } catch { $ErrorMessage = Get-CippException -Exception $_ $Message = "Failed to exclude from CA policy '$($Policy.displayName)': $($ErrorMessage.NormalizedError)" - Write-LogMessage -Headers $Headers -API $APIName -tenant $Tenant -message $Message -Sev 'Warning' -LogData $ErrorMessage + Write-LogMessage -Headers $Headers -API $APIName -tenant $Tenant -message $Message -sev 'Warn' -LogData $ErrorMessage $Results.Add($Message) } } @@ -85,7 +85,7 @@ function Invoke-ExecHVEUser { } catch { $ErrorMessage = Get-CippException -Exception $_ $Message = "Failed to check/update Conditional Access policies: $($ErrorMessage.NormalizedError)" - Write-LogMessage -Headers $Headers -API $APIName -tenant $Tenant -message $Message -Sev 'Warning' -LogData $ErrorMessage + Write-LogMessage -Headers $Headers -API $APIName -tenant $Tenant -message $Message -sev 'Warn' -LogData $ErrorMessage $Results.Add($Message) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyCalPerms.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyCalPerms.ps1 index 562fb1dd2f92..24f88d8cd971 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyCalPerms.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyCalPerms.ps1 @@ -105,7 +105,7 @@ function Invoke-ExecModifyCalPerms { } if ($Results.Count -eq 0) { - Write-LogMessage -headers $Headers -API $APIName -message 'No results were generated from the operation' -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message 'No results were generated from the operation' -sev 'Warn' $Results.Add('No results were generated from the operation. Please check the logs for more details.') $HasErrors = $true } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyContactPerms.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyContactPerms.ps1 index 75877cbbdb9c..ad659857cc75 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyContactPerms.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyContactPerms.ps1 @@ -104,7 +104,7 @@ function Invoke-ExecModifyContactPerms { } if ($Results.Count -eq 0) { - Write-LogMessage -headers $Headers -API $APIName -message 'No results were generated from the operation' -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message 'No results were generated from the operation' -sev 'Warn' $Results.Add('No results were generated from the operation. Please check the logs for more details.') $HasErrors = $true } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyMBPerms.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyMBPerms.ps1 index 4d768e96cc1a..5e051053f90b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyMBPerms.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyMBPerms.ps1 @@ -293,7 +293,7 @@ Function Invoke-ExecModifyMBPerms { } if ($CmdletArray.Count -eq 0) { - Write-LogMessage -headers $Request.Headers -API $APINAME -message 'No valid cmdlets to process' -Sev 'Warning' -tenant $TenantFilter + Write-LogMessage -headers $Request.Headers -API $APINAME -message 'No valid cmdlets to process' -sev 'Warn' -tenant $TenantFilter $body = [pscustomobject]@{'Results' = @("No valid permission changes to process") } return ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK @@ -327,7 +327,7 @@ Function Invoke-ExecModifyMBPerms { Write-LogMessage -headers $Request.Headers -API $APINAME -message "Success for operation $operationGuid`: $($metadata.ExpectedResult)" -Sev 'Info' -tenant $TenantFilter } } else { - Write-LogMessage -headers $Request.Headers -API $APINAME -message "Could not map result to operation. GUID: $operationGuid, Available GUIDs: $($GuidToMetadataMap.Keys -join ', ')" -Sev 'Warning' -tenant $TenantFilter + Write-LogMessage -headers $Request.Headers -API $APINAME -message "Could not map result to operation. GUID: $operationGuid, Available GUIDs: $($GuidToMetadataMap.Keys -join ', ')" -sev 'Warn' -tenant $TenantFilter # Fallback for unmapped results if ($result.error) { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddJITAdminTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddJITAdminTemplate.ps1 index dd2de9d523e0..7f8777f94899 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddJITAdminTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-AddJITAdminTemplate.ps1 @@ -61,7 +61,7 @@ function Invoke-AddJITAdminTemplate { Write-LogMessage -headers $Headers -API $APIName -message "Unset default flag for existing template: $($data.templateName)" -Sev 'Info' } } catch { - Write-LogMessage -headers $Headers -API $APIName -message "Failed to update existing template: $($_.Exception.Message)" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message "Failed to update existing template: $($_.Exception.Message)" -sev 'Warn' } } } @@ -104,7 +104,7 @@ function Invoke-AddJITAdminTemplate { if (![string]::IsNullOrWhiteSpace($Request.Body.defaultUserName)) { $TemplateObject.defaultUserName = $Request.Body.defaultUserName } - + # defaultDomain is only saved for specific tenant templates (not AllTenants) if ($TenantFilter -ne 'AllTenants' -and $Request.Body.defaultDomain) { if ($Request.Body.defaultDomain -is [string]) { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index 6da71f0a98bf..b36cf8ea3987 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -16,7 +16,6 @@ function Invoke-CIPPOffboardingJob { } Write-Information "Starting offboarding job for $Username in tenant $TenantFilter" - Write-LogMessage -API 'Offboarding' -tenant $TenantFilter -message "Starting offboarding orchestration for user $Username" -sev Info # Get user information needed for various tasks $User = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($Username)?`$select=id,displayName,onPremisesSyncEnabled,onPremisesImmutableId" -tenantid $TenantFilter @@ -36,6 +35,7 @@ function Invoke-CIPPOffboardingJob { username = $Username userid = $UserID APIName = $APIName + Headers = $Headers } } @{ @@ -46,6 +46,7 @@ function Invoke-CIPPOffboardingJob { DisplayName = $DisplayName UserID = $Username APIName = $APIName + Headers = $Headers } } @{ @@ -56,6 +57,7 @@ function Invoke-CIPPOffboardingJob { userid = $Username AccountEnabled = $false APIName = $APIName + Headers = $Headers } } @{ @@ -66,6 +68,7 @@ function Invoke-CIPPOffboardingJob { UserID = $Username hidefromgal = $true APIName = $APIName + Headers = $Headers } } @{ @@ -76,6 +79,7 @@ function Invoke-CIPPOffboardingJob { tenantFilter = $TenantFilter APIName = $APIName Username = $Username + Headers = $Headers } } @{ @@ -87,6 +91,7 @@ function Invoke-CIPPOffboardingJob { tenantFilter = $TenantFilter APIName = $APIName RemoveAllRules = $true + Headers = $Headers } } @{ @@ -97,6 +102,7 @@ function Invoke-CIPPOffboardingJob { username = $Username tenantFilter = $TenantFilter APIName = $APIName + Headers = $Headers } } @{ @@ -107,6 +113,7 @@ function Invoke-CIPPOffboardingJob { Username = $Username TenantFilter = $TenantFilter APIName = $APIName + Headers = $Headers } } @{ @@ -119,6 +126,7 @@ function Invoke-CIPPOffboardingJob { ExternalMessage = $Options.OOO APIName = $APIName state = 'Enabled' + Headers = $Headers } } @{ @@ -131,6 +139,7 @@ function Invoke-CIPPOffboardingJob { Forward = $Options.forward.value KeepCopy = [bool]$Options.KeepCopy APIName = $APIName + Headers = $Headers } } @{ @@ -142,6 +151,7 @@ function Invoke-CIPPOffboardingJob { tenantFilter = $TenantFilter Disable = $true APIName = $APIName + Headers = $Headers } } @{ @@ -152,6 +162,7 @@ function Invoke-CIPPOffboardingJob { userid = $Username OnedriveAccessUser = $Options.OnedriveAccess APIName = $APIName + Headers = $Headers } } @{ @@ -164,6 +175,7 @@ function Invoke-CIPPOffboardingJob { Automap = $false AccessRights = @('FullAccess') APIName = $APIName + Headers = $Headers } } @{ @@ -176,6 +188,7 @@ function Invoke-CIPPOffboardingJob { Automap = $true AccessRights = @('FullAccess') APIName = $APIName + Headers = $Headers } } @{ @@ -186,6 +199,7 @@ function Invoke-CIPPOffboardingJob { TenantFilter = $TenantFilter UseCache = $true APIName = $APIName + Headers = $Headers } } @{ @@ -196,6 +210,7 @@ function Invoke-CIPPOffboardingJob { TenantFilter = $TenantFilter UseCache = $true APIName = $APIName + Headers = $Headers } } @{ @@ -207,6 +222,7 @@ function Invoke-CIPPOffboardingJob { username = $Username MailboxType = 'Shared' APIName = $APIName + Headers = $Headers } } @{ @@ -215,6 +231,8 @@ function Invoke-CIPPOffboardingJob { Parameters = @{ UserPrincipalName = $Username TenantFilter = $TenantFilter + APIName = $APIName + Headers = $Headers } } @{ @@ -225,6 +243,7 @@ function Invoke-CIPPOffboardingJob { username = $Username tenantFilter = $TenantFilter APIName = $APIName + Headers = $Headers } } @{ @@ -236,6 +255,7 @@ function Invoke-CIPPOffboardingJob { tenantFilter = $TenantFilter APIName = $APIName Schedule = $true + Headers = $Headers } } @{ @@ -247,6 +267,7 @@ function Invoke-CIPPOffboardingJob { TenantFilter = $TenantFilter User = $User APIName = $APIName + Headers = $Headers } } @{ @@ -257,6 +278,7 @@ function Invoke-CIPPOffboardingJob { Username = $Username TenantFilter = $TenantFilter APIName = $APIName + Headers = $Headers } } ) @@ -273,7 +295,7 @@ function Invoke-CIPPOffboardingJob { } if ($Batch.Count -eq 0) { - Write-LogMessage -API 'Offboarding' -tenant $TenantFilter -message "No offboarding tasks selected for user $Username" -sev Warning + Write-LogMessage -API $APIName -tenant $TenantFilter -message "No offboarding tasks selected for user $Username" -sev Warning return "No offboarding tasks were selected for $Username" } @@ -288,20 +310,19 @@ function Invoke-CIPPOffboardingJob { } # Add post-execution handler if TaskInfo is provided (from scheduled task) - if ($TaskInfo) { - $InputObject | Add-Member -NotePropertyName PostExecution -NotePropertyValue @{ - FunctionName = 'CIPPOffboardingComplete' - Parameters = @{ - TaskInfo = $TaskInfo - TenantFilter = $TenantFilter - Username = $Username - } + $InputObject | Add-Member -NotePropertyName PostExecution -NotePropertyValue @{ + FunctionName = 'CIPPOffboardingComplete' + Parameters = @{ + TaskInfo = $TaskInfo ?? $null + TenantFilter = $TenantFilter + Username = $Username + Headers = $Headers } } $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 10 -Compress) Write-Information "Started offboarding job for $Username with ID = '$InstanceId'" - Write-LogMessage -API 'Offboarding' -tenant $TenantFilter -message "Started offboarding job for $Username with $($Batch.Count) tasks. Instance ID: $InstanceId" -sev Info + Write-LogMessage -API $APIName -tenant $TenantFilter -message "Started offboarding job for $Username with $($Batch.Count) tasks. Instance ID: $InstanceId" -sev Info return "Offboarding job started for $Username with $($Batch.Count) tasks" diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditJITAdminTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditJITAdminTemplate.ps1 index 35bbd95139ca..868208b51e87 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditJITAdminTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditJITAdminTemplate.ps1 @@ -76,7 +76,7 @@ function Invoke-EditJITAdminTemplate { Write-LogMessage -headers $Headers -API $APIName -message "Unset default flag for existing template: $($data.templateName)" -Sev 'Info' } } catch { - Write-LogMessage -headers $Headers -API $APIName -message "Failed to update existing template: $($_.Exception.Message)" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message "Failed to update existing template: $($_.Exception.Message)" -sev 'Warn' } } } @@ -121,7 +121,7 @@ function Invoke-EditJITAdminTemplate { if (![string]::IsNullOrWhiteSpace($Request.Body.defaultUserName)) { $TemplateObject.defaultUserName = $Request.Body.defaultUserName } - + # defaultDomain is only saved for specific tenant templates (not AllTenants) if ($TenantFilter -ne 'AllTenants' -and $Request.Body.defaultDomain) { if ($Request.Body.defaultDomain -is [string]) { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListJITAdminTemplates.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListJITAdminTemplates.ps1 index aa5ad886758e..3a99bf6d7337 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListJITAdminTemplates.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListJITAdminTemplates.ps1 @@ -34,7 +34,7 @@ function Invoke-ListJITAdminTemplates { $data | Add-Member -NotePropertyName 'RowKey' -NotePropertyValue $row.RowKey -Force $data } catch { - Write-LogMessage -headers $Headers -API $APIName -message "Failed to process JIT Admin template: $($row.RowKey) - $($_.Exception.Message)" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message "Failed to process JIT Admin template: $($row.RowKey) - $($_.Exception.Message)" -sev 'Warn' } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveJITAdminTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveJITAdminTemplate.ps1 index e0ac56a8f294..ae25c9b97c18 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveJITAdminTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveJITAdminTemplate.ps1 @@ -13,7 +13,7 @@ function Invoke-RemoveJITAdminTemplate { try { $ID = $Request.Query.ID ?? $Request.Body.ID - + if ([string]::IsNullOrWhiteSpace($ID)) { throw 'ID is required' } @@ -29,7 +29,7 @@ function Invoke-RemoveJITAdminTemplate { $StatusCode = [HttpStatusCode]::OK } else { $Result = "JIT Admin Template with ID $ID not found" - Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message $Result -sev 'Warn' $StatusCode = [HttpStatusCode]::NotFound } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveUserDefaultTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveUserDefaultTemplate.ps1 index c97f52617d00..3efb129e21eb 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveUserDefaultTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveUserDefaultTemplate.ps1 @@ -24,7 +24,7 @@ function Invoke-RemoveUserDefaultTemplate { $StatusCode = [HttpStatusCode]::OK } else { $Result = "User Default Template with ID $ID not found" - Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message $Result -sev 'Warn' $StatusCode = [HttpStatusCode]::NotFound } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-AddSafeLinksPolicyFromTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-AddSafeLinksPolicyFromTemplate.ps1 index a48db0eaccaf..2c02b5b70942 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-AddSafeLinksPolicyFromTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-AddSafeLinksPolicyFromTemplate.ps1 @@ -82,13 +82,13 @@ Function Invoke-AddSafeLinksPolicyFromTemplate { # Check if policy already exists if (Test-PolicyExists -TenantFilter $TenantFilter -PolicyName $PolicyName) { - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Policy '$PolicyName' already exists" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Policy '$PolicyName' already exists" -sev 'Warn' return "Policy '$PolicyName' already exists in tenant $TenantFilter" } # Check if rule already exists if (Test-RuleExists -TenantFilter $TenantFilter -RuleName $RuleName) { - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Rule '$RuleName' already exists" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Rule '$RuleName' already exists" -sev 'Warn' return "Rule '$RuleName' already exists in tenant $TenantFilter" } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecDeleteSafeLinksPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecDeleteSafeLinksPolicy.ps1 index f3fa33bcaa46..4f124a557e9a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecDeleteSafeLinksPolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecDeleteSafeLinksPolicy.ps1 @@ -39,7 +39,7 @@ function Invoke-ExecDeleteSafeLinksPolicy { catch { $ErrorMessage = Get-CippException -Exception $_ $ResultMessages.Add("Failed to delete SafeLinks rule '$RuleName'. Error: $($ErrorMessage.NormalizedError)") | Out-Null - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to delete SafeLinks rule '$RuleName'. Error: $($ErrorMessage.NormalizedError)" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to delete SafeLinks rule '$RuleName'. Error: $($ErrorMessage.NormalizedError)" -sev 'Warn' } } else { @@ -66,7 +66,7 @@ function Invoke-ExecDeleteSafeLinksPolicy { catch { $ErrorMessage = Get-CippException -Exception $_ $ResultMessages.Add("Failed to delete SafeLinks policy '$PolicyName'. Error: $($ErrorMessage.NormalizedError)") | Out-Null - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to delete SafeLinks policy '$PolicyName'. Error: $($ErrorMessage.NormalizedError)" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to delete SafeLinks policy '$PolicyName'. Error: $($ErrorMessage.NormalizedError)" -sev 'Warn' } } else { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecNewSafeLinksPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecNewSafeLinksPolicy.ps1 index dbf3790b3090..dafc18eb2dcc 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecNewSafeLinksPolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ExecNewSafeLinksPolicy.ps1 @@ -124,13 +124,13 @@ function Invoke-ExecNewSafeLinksPolicy { try { # Check if policy already exists if (Test-PolicyExists -TenantFilter $TenantFilter -PolicyName $PolicyName) { - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Policy '$PolicyName' already exists" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Policy '$PolicyName' already exists" -sev 'Warn' return "Policy '$PolicyName' already exists in tenant $TenantFilter" } # Check if rule already exists if (Test-RuleExists -TenantFilter $TenantFilter -RuleName $RuleName) { - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Rule '$RuleName' already exists" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Rule '$RuleName' already exists" -sev 'Warn' return "Rule '$RuleName' already exists in tenant $TenantFilter" } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicy.ps1 index 8c4ec6595652..e679b579b329 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicy.ps1 @@ -179,7 +179,7 @@ Function Invoke-ListSafeLinksPolicy { $BuiltInOnlyConfigs = ($SortedOutput | Where-Object { $_.ConfigurationStatus -like "*Built-In Rule Only*" }).Count if ($PolicyOnlyConfigs -gt 0 -or $RuleOnlyConfigs -gt 0) { - Write-LogMessage -headers $Headers -API $APIName -message "Found $($PolicyOnlyConfigs + $RuleOnlyConfigs) orphaned SafeLinks configurations that may need attention" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message "Found $($PolicyOnlyConfigs + $RuleOnlyConfigs) orphaned SafeLinks configurations that may need attention" -sev 'Warn' } $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicyDetails.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicyDetails.ps1 index 89840a6a07ed..5f8167531d3e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicyDetails.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-ListSafeLinksPolicyDetails.ps1 @@ -43,7 +43,7 @@ function Invoke-ListSafeLinksPolicyDetails { catch { $ErrorMessage = Get-CippException -Exception $_ $LogMessages.Add("Failed to retrieve details for SafeLinks policy '$PolicyName'. Error: $($ErrorMessage.NormalizedError)") | Out-Null - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to retrieve details for SafeLinks policy '$PolicyName'. Error: $($ErrorMessage.NormalizedError)" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to retrieve details for SafeLinks policy '$PolicyName'. Error: $($ErrorMessage.NormalizedError)" -sev 'Warn' $Result.PolicyError = "Failed to retrieve: $($ErrorMessage.NormalizedError)" } } @@ -72,7 +72,7 @@ function Invoke-ListSafeLinksPolicyDetails { catch { $ErrorMessage = Get-CippException -Exception $_ $LogMessages.Add("Failed to retrieve details for SafeLinks rule '$RuleName'. Error: $($ErrorMessage.NormalizedError)") | Out-Null - Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to retrieve details for SafeLinks rule '$RuleName'. Error: $($ErrorMessage.NormalizedError)" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to retrieve details for SafeLinks rule '$RuleName'. Error: $($ErrorMessage.NormalizedError)" -sev 'Warn' $Result.RuleError = "Failed to retrieve: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecCreateAppTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecCreateAppTemplate.ps1 index 23315d7eb700..520536881638 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecCreateAppTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecCreateAppTemplate.ps1 @@ -123,7 +123,7 @@ function Invoke-ExecCreateAppTemplate { $Permissions = @($DelegateResourceAccess) + @($ApplicationResourceAccess) | Where-Object { $_ -ne $null } if ($Permissions.Count -eq 0) { - Write-LogMessage -headers $Request.headers -API $APINAME -message "No permissions found for $AppId via any method" -Sev 'Warning' + Write-LogMessage -headers $Request.headers -API $APINAME -message "No permissions found for $AppId via any method" -sev 'Warn' } else { Write-LogMessage -headers $Request.headers -API $APINAME -message "Extracted $($Permissions.Count) resource permission(s) from service principal grants" -Sev 'Info' } @@ -245,7 +245,7 @@ function Invoke-ExecCreateAppTemplate { }) $RequestIndex++ } else { - Write-LogMessage -headers $Request.headers -API $APINAME -message "Service principal not found in tenant for appId: $ResourceAppId" -Sev 'Warning' + Write-LogMessage -headers $Request.headers -API $APINAME -message "Service principal not found in tenant for appId: $ResourceAppId" -sev 'Warn' } } @@ -274,7 +274,7 @@ function Invoke-ExecCreateAppTemplate { $ResourceSP = $SPLookup[$ResourceAppId] if (!$ResourceSP) { - Write-LogMessage -headers $Request.headers -API $APINAME -message "Service principal not found for appId: $ResourceAppId - skipping permission translation" -Sev 'Warning' + Write-LogMessage -headers $Request.headers -API $APINAME -message "Service principal not found for appId: $ResourceAppId - skipping permission translation" -sev 'Warn' continue } @@ -291,7 +291,7 @@ function Invoke-ExecCreateAppTemplate { } [void]$AppPerms.Add($PermObj) } else { - Write-LogMessage -headers $Request.headers -API $APINAME -message "Application permission $($Access.id) not found in $ResourceAppId appRoles" -Sev 'Warning' + Write-LogMessage -headers $Request.headers -API $APINAME -message "Application permission $($Access.id) not found in $ResourceAppId appRoles" -sev 'Warn' } } elseif ($Access.type -eq 'Scope') { Write-Information "Processing delegated permission with id $($Access.id) for resource appId $ResourceAppId" @@ -364,14 +364,14 @@ function Invoke-ExecCreateAppTemplate { $PermissionSetId = $TemplateData.PermissionSetId Write-LogMessage -headers $Request.headers -API $APINAME -message "Found existing permission set ID: $PermissionSetId in template" -Sev 'Info' } else { - Write-LogMessage -headers $Request.headers -API $APINAME -message 'Existing template found but has no PermissionSetId' -Sev 'Warning' + Write-LogMessage -headers $Request.headers -API $APINAME -message 'Existing template found but has no PermissionSetId' -sev 'Warn' } break } } } catch { # Ignore lookup errors - Write-LogMessage -headers $Request.headers -API $APINAME -message "Error during template lookup: $($_.Exception.Message)" -Sev 'Warning' + Write-LogMessage -headers $Request.headers -API $APINAME -message "Error during template lookup: $($_.Exception.Message)" -sev 'Warn' } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 index c0fa299b5466..e114d1a32977 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 @@ -94,7 +94,7 @@ function Invoke-ListTenants { try { $Tenant | Add-Member -MemberType NoteProperty -Name 'offboardingDefaults' -Value ($TenantDefaults.Value | ConvertFrom-Json) -Force } catch { - Write-LogMessage -headers $Headers -API $APIName -message "Failed to parse offboarding defaults for tenant $($Tenant.customerId): $($_.Exception.Message)" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message "Failed to parse offboarding defaults for tenant $($Tenant.customerId): $($_.Exception.Message)" -sev 'Warn' $Tenant | Add-Member -MemberType NoteProperty -Name 'offboardingDefaults' -Value $null -Force } } else { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPRoleTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPRoleTemplate.ps1 index 07018acad177..17f648740620 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPRoleTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPRoleTemplate.ps1 @@ -18,7 +18,7 @@ function Invoke-ExecGDAPRoleTemplate { if ($Request.Query.TemplateId) { $Template = $Templates | Where-Object -Property RowKey -EQ $Request.Query.TemplateId if (!$Template) { - Write-LogMessage -headers $Headers -API $APIName -message "GDAP role template '$($Request.Query.TemplateId)' not found" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message "GDAP role template '$($Request.Query.TemplateId)' not found" -sev 'Warn' $Body = @{} } else { Write-LogMessage -headers $Headers -API $APIName -message "Retrieved GDAP role template '$($Request.Query.TemplateId)'" -Sev 'Info' @@ -50,7 +50,7 @@ function Invoke-ExecGDAPRoleTemplate { $Template = $Templates | Where-Object -Property RowKey -EQ $OriginalRowKey if ($Template) { $RoleMappings = $Request.Body.RoleMappings - + # If the template ID is being changed, delete the old one and create a new one if ($OriginalRowKey -ne $NewRowKey) { Remove-AzDataTableEntity -Force @Table -Entity $Template @@ -68,7 +68,7 @@ function Invoke-ExecGDAPRoleTemplate { } } } else { - Write-LogMessage -headers $Headers -API $APIName -message "GDAP role template '$OriginalRowKey' not found for editing" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message "GDAP role template '$OriginalRowKey' not found for editing" -sev 'Warn' $Body = @{ Results = "Template $OriginalRowKey not found" } @@ -84,7 +84,7 @@ function Invoke-ExecGDAPRoleTemplate { Results = "Deleted template $RowKey" } } else { - Write-LogMessage -headers $Headers -API $APIName -message "GDAP role template '$RowKey' not found for deletion" -Sev 'Warning' + Write-LogMessage -headers $Headers -API $APIName -message "GDAP role template '$RowKey' not found for deletion" -sev 'Warn' $Body = @{ Results = "Template $RowKey not found" } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1 index 63b639e3769e..90dcfc65b713 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1 @@ -100,7 +100,7 @@ function Invoke-ExecUpdateDriftDeviation { Write-LogMessage -tenant $TenantFilter -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Deleted Policy with ID $($ID)" -Sev 'Info' } else { "could not find policy with ID $($ID)" - Write-LogMessage -tenant $TenantFilter -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Could not find Policy with ID $($ID) to delete for remediation" -Sev 'Warning' + Write-LogMessage -tenant $TenantFilter -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Could not find Policy with ID $($ID) to delete for remediation" -sev 'Warn' } diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecListAppId.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecListAppId.ps1 index 6c0581ad0c8e..27a369092a08 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecListAppId.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecListAppId.ps1 @@ -83,13 +83,13 @@ function Invoke-ExecListAppId { Invoke-GraphRequest -Method PATCH -Url "https://graph.microsoft.com/v1.0/applications/$($AppResponse.body.id)" -Body $AppUpdateBody -tenantid $env:TenantID -NoAuthCheck $true Write-LogMessage -message "Updated redirect URIs for application $($env:ApplicationID) to include $NewRedirectUri" -Sev 'Info' } catch { - Write-LogMessage -message "Failed to update redirect URIs for application $($env:ApplicationID)" -LogData (Get-CippException -Exception $_) -Sev 'Warning' + Write-LogMessage -message "Failed to update redirect URIs for application $($env:ApplicationID)" -LogData (Get-CippException -Exception $_) -sev 'Warn' } } } } } catch { - Write-LogMessage -message 'Failed to retrieve organization info and authenticated user' -LogData (Get-CippException -Exception $_) -Sev 'Warning' + Write-LogMessage -message 'Failed to retrieve organization info and authenticated user' -LogData (Get-CippException -Exception $_) -sev 'Warn' } $Results = @{ diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 index 8c4d4953bdf0..bd708947c66f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 @@ -82,7 +82,7 @@ function Invoke-ListLogs { } } else { if ($request.Query.Filter -eq 'True') { - $LogLevel = if ($Request.Query.Severity) { ($Request.query.Severity).split(',') } else { 'Info', 'Warn', 'Error', 'Critical', 'Alert' } + $LogLevel = if ($Request.Query.Severity) { ($Request.query.Severity).split(',') } else { 'Info', 'Warn', 'Warning', 'Error', 'Critical', 'Alert' } $PartitionKey = $Request.Query.DateFilter $username = $Request.Query.User ?? '*' $TenantFilter = $Request.Query.Tenant @@ -102,7 +102,7 @@ function Invoke-ListLogs { $Filter = "PartitionKey eq '{0}'" -f (Get-Date -UFormat '%Y%m%d') } } else { - $LogLevel = 'Info', 'Warn', 'Error', 'Critical', 'Alert' + $LogLevel = 'Info', 'Warn', 'Warning', 'Error', 'Critical', 'Alert' $PartitionKey = Get-Date -UFormat '%Y%m%d' $username = '*' $TenantFilter = $null diff --git a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-BackupRetentionCleanup.ps1 b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-BackupRetentionCleanup.ps1 index ed00a0292aa1..b8edf3aedd94 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-BackupRetentionCleanup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-BackupRetentionCleanup.ps1 @@ -64,7 +64,7 @@ function Start-BackupRetentionCleanup { $BlobDeletedCount++ Write-Host "Deleted blob: $BlobPath" } catch { - Write-LogMessage -API 'BackupRetentionCleanup' -message "Failed to delete blob $($Backup.Backup): $($_.Exception.Message)" -Sev 'Warning' + Write-LogMessage -API 'BackupRetentionCleanup' -message "Failed to delete blob $($Backup.Backup): $($_.Exception.Message)" -sev 'Warn' } } } @@ -124,7 +124,7 @@ function Start-BackupRetentionCleanup { $BlobDeletedCount++ Write-Host "Deleted blob: $BlobPath" } catch { - Write-LogMessage -API 'BackupRetentionCleanup' -message "Failed to delete blob $($Backup.Backup): $($_.Exception.Message)" -Sev 'Warning' + Write-LogMessage -API 'BackupRetentionCleanup' -message "Failed to delete blob $($Backup.Backup): $($_.Exception.Message)" -sev 'Warn' } } } diff --git a/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 b/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 index d750ce6cdea9..4ef75831b703 100644 --- a/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 @@ -103,7 +103,7 @@ function New-CIPPRestoreTask { } } catch { $restorationStats['CustomVariables'].failed++ - Write-LogMessage -message "Failed to restore custom variable $($variable.RowKey): $($_.Exception.Message)" -Sev 'Warning' + Write-LogMessage -message "Failed to restore custom variable $($variable.RowKey): $($_.Exception.Message)" -sev 'Warn' $RestoreData.Add("Failed to restore custom variable $($variable.RowKey)") } } diff --git a/Modules/CIPPCore/Public/Remove-CIPPCalendarPermissions.ps1 b/Modules/CIPPCore/Public/Remove-CIPPCalendarPermissions.ps1 index 402eca0145b9..9a547a29f1ec 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPCalendarPermissions.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPCalendarPermissions.ps1 @@ -112,9 +112,9 @@ function Remove-CIPPCalendarPermissions { } catch { Write-Verbose "Failed to sync cache: $_" } - + $ErrorMsg = "Failed to remove $UserToRemove from calendar $($CalPermEntry.CalendarUPN): $($_.Exception.Message)" - Write-LogMessage -headers $Headers -API $APIName -message $ErrorMsg -Sev 'Warning' -tenant $TenantFilter + Write-LogMessage -headers $Headers -API $APIName -message $ErrorMsg -sev 'Warn' -tenant $TenantFilter $Results.Add($ErrorMsg) } } @@ -156,7 +156,7 @@ function Remove-CIPPCalendarPermissions { # Sync cache even on error (permission might not exist) $MailboxUPN = if ($CalendarIdentity -match '^([^:]+):') { $Matches[1] } else { $CalendarIdentity } $Folder = if ($CalendarIdentity -match ':\\(.+)$') { $Matches[1] } else { $FolderName } - + try { Sync-CIPPCalendarPermissionCache -TenantFilter $TenantFilter -MailboxIdentity $MailboxUPN -FolderName $Folder -User $UserToRemove -Action 'Remove' } catch { diff --git a/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 b/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 index f901ac82953e..bec2daa302b3 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 @@ -8,102 +8,111 @@ function Remove-CIPPGroups { $UserID ) - $BulkInfoRequests = [System.Collections.Generic.List[object]]::new() + try { - if (-not $UserID) { + $BulkInfoRequests = [System.Collections.Generic.List[object]]::new() + + if (-not $UserID) { + $BulkInfoRequests.Add(@{ + id = 'getUserID' + method = 'GET' + url = "users/$($Username)?`$select=id" + }) + } + + $BulkInfoRequests.Add( + @{ + id = 'getAllGroups' + method = 'GET' + url = "groups/?`$select=displayName,mailEnabled,id,groupTypes,assignedLicenses,onPremisesSyncEnabled,membershipRule&`$top=999" + }) $BulkInfoRequests.Add(@{ - id = 'getUserID' + id = 'getUserGroups' method = 'GET' - url = "users/$($Username)?`$select=id" + url = "users/$($UserID ?? $Username)/memberOf/microsoft.graph.group?`$select=id" }) - } - $BulkInfoRequests.Add( - @{ - id = 'getAllGroups' - method = 'GET' - url = "groups/?`$select=displayName,mailEnabled,id,groupTypes,assignedLicenses,onPremisesSyncEnabled,membershipRule&`$top=999" - }) - $BulkInfoRequests.Add(@{ - id = 'getUserGroups' - method = 'GET' - url = "users/$($UserID ?? $Username)/memberOf/microsoft.graph.group?`$select=id" - }) - - $BulkGetResults = New-GraphBulkRequest -tenantid $TenantFilter -Requests @($BulkInfoRequests) - - $UserInfo = ($BulkGetResults | Where-Object { $_.id -eq 'getUserID' }).body - if ($UserInfo) { - $UserID = $UserInfo.id - } - $AllGroups = ($BulkGetResults | Where-Object { $_.id -eq 'getAllGroups' }).body.value - $UserGroups = ($BulkGetResults | Where-Object { $_.id -eq 'getUserGroups' }).body.value - - #users/$($User.id)/memberOf/microsoft.graph.directoryRole - if (-not $UserGroups) { - $Returnval = "$($Username) is not a member of any groups." - Write-LogMessage -headers $Headers -API $APIName -message "$($Username) is not a member of any groups" -Sev 'Info' -tenant $TenantFilter - return $Returnval - } + $BulkGetResults = New-GraphBulkRequest -tenantid $TenantFilter -Requests @($BulkInfoRequests) + + $UserInfo = ($BulkGetResults | Where-Object { $_.id -eq 'getUserID' }).body + if ($UserInfo) { + $UserID = $UserInfo.id + } + $AllGroups = ($BulkGetResults | Where-Object { $_.id -eq 'getAllGroups' }).body.value + $UserGroups = ($BulkGetResults | Where-Object { $_.id -eq 'getUserGroups' }).body.value + + #users/$($User.id)/memberOf/microsoft.graph.directoryRole + if (-not $UserGroups) { + $Returnval = "$($Username) is not a member of any groups." + Write-LogMessage -headers $Headers -API $APIName -message "$($Username) is not a member of any groups" -Sev 'Info' -tenant $TenantFilter + return $Returnval + } + + Write-Information "Initiating group membership removal for user: $Username in tenant: $TenantFilter" + + # Initialize bulk request arrays and results + $BulkRequests = [System.Collections.Generic.List[object]]::new() + $ExoBulkRequests = [System.Collections.Generic.List[object]]::new() + $GraphLogs = [System.Collections.Generic.List[object]]::new() + $ExoLogs = [System.Collections.Generic.List[object]]::new() + $Results = [System.Collections.Generic.List[string]]::new() + + # Process each group and prepare bulk requests + foreach ($Group in $UserGroups) { + $GroupInfo = $AllGroups | Where-Object -Property id -EQ $Group.id + $GroupName = $GroupInfo.displayName + $IsMailEnabled = $GroupInfo.mailEnabled + $IsM365Group = $GroupInfo.groupTypes -and $GroupInfo.groupTypes -contains 'Unified' + $IsLicensed = $GroupInfo.assignedLicenses.Count -gt 0 + $IsDynamic = -not [string]::IsNullOrWhiteSpace($GroupInfo.membershipRule) - # Initialize bulk request arrays and results - $BulkRequests = [System.Collections.Generic.List[object]]::new() - $ExoBulkRequests = [System.Collections.Generic.List[object]]::new() - $GraphLogs = [System.Collections.Generic.List[object]]::new() - $ExoLogs = [System.Collections.Generic.List[object]]::new() - $Results = [System.Collections.Generic.List[string]]::new() - - # Process each group and prepare bulk requests - foreach ($Group in $UserGroups) { - $GroupInfo = $AllGroups | Where-Object -Property id -EQ $Group.id - $GroupName = $GroupInfo.displayName - $IsMailEnabled = $GroupInfo.mailEnabled - $IsM365Group = $GroupInfo.groupTypes -and $GroupInfo.groupTypes -contains 'Unified' - $IsLicensed = $GroupInfo.assignedLicenses.Count -gt 0 - $IsDynamic = -not [string]::IsNullOrWhiteSpace($GroupInfo.membershipRule) - - if ($IsLicensed) { - $Results.Add("Could not remove $Username from group '$GroupName' because it has assigned licenses. These groups are removed during the license removal step.") - Write-LogMessage -headers $Headers -API $APIName -message "Could not remove $Username from group '$GroupName' because it has assigned licenses. These groups are removed during the license removal step." -Sev 'Warning' -tenant $TenantFilter - } elseif ($IsDynamic) { - $Results.Add("Error: Could not remove $Username from group '$GroupName' because it is a Dynamic Group.") - Write-LogMessage -headers $Headers -API $APIName -message "Could not remove $Username from group '$GroupName' because it is a Dynamic Group." -Sev 'Warning' -tenant $TenantFilter - } elseif ($GroupInfo.onPremisesSyncEnabled) { - $Results.Add("Error: Could not remove $Username from group '$GroupName' because it is synced with Active Directory.") - Write-LogMessage -headers $Headers -API $APIName -message "Could not remove $Username from group '$GroupName' because it is synced with Active Directory." -Sev 'Warning' -tenant $TenantFilter - } else { - if ($IsM365Group -or (-not $IsMailEnabled)) { - # Use Graph API for M365 Groups and Security Groups - $BulkRequests.Add(@{ - id = "removeFromGroup-$($Group.id)" - method = 'DELETE' - url = "groups/$($Group.id)/members/$UserID/`$ref" - }) - $GraphLogs.Add(@{ - message = "Removed $Username from $GroupName" - id = "removeFromGroup-$($Group.id)" - groupName = $GroupName - }) - } elseif ($IsMailEnabled) { - # Use Exchange Online for Distribution Lists - $Params = @{ - Identity = $GroupName - Member = $UserID - BypassSecurityGroupManagerCheck = $true + if ($IsLicensed) { + $Results.Add("Could not remove $Username from group '$GroupName' because it has assigned licenses. These groups are removed during the license removal step.") + Write-LogMessage -headers $Headers -API $APIName -message "Could not remove $Username from group '$GroupName' because it has assigned licenses. These groups are removed during the license removal step." -sev 'Warn' -tenant $TenantFilter + } elseif ($IsDynamic) { + $Results.Add("Error: Could not remove $Username from group '$GroupName' because it is a Dynamic Group.") + Write-LogMessage -headers $Headers -API $APIName -message "Could not remove $Username from group '$GroupName' because it is a Dynamic Group." -sev 'Warn' -tenant $TenantFilter + } elseif ($GroupInfo.onPremisesSyncEnabled) { + $Results.Add("Error: Could not remove $Username from group '$GroupName' because it is synced with Active Directory.") + Write-LogMessage -headers $Headers -API $APIName -message "Could not remove $Username from group '$GroupName' because it is synced with Active Directory." -sev 'Warn' -tenant $TenantFilter + } else { + if ($IsM365Group -or (-not $IsMailEnabled)) { + # Use Graph API for M365 Groups and Security Groups + $BulkRequests.Add(@{ + id = "removeFromGroup-$($Group.id)" + method = 'DELETE' + url = "groups/$($Group.id)/members/$UserID/`$ref" + }) + $GraphLogs.Add(@{ + message = "Removed $Username from $GroupName" + id = "removeFromGroup-$($Group.id)" + groupName = $GroupName + }) + } elseif ($IsMailEnabled) { + # Use Exchange Online for Distribution Lists + $Params = @{ + Identity = $GroupName + Member = $UserID + BypassSecurityGroupManagerCheck = $true + } + $ExoBulkRequests.Add(@{ + CmdletInput = @{ + CmdletName = 'Remove-DistributionGroupMember' + Parameters = $Params + } + }) + $ExoLogs.Add(@{ + message = "Removed $Username from $GroupName" + target = $UserID + groupName = $GroupName + }) } - $ExoBulkRequests.Add(@{ - CmdletInput = @{ - CmdletName = 'Remove-DistributionGroupMember' - Parameters = $Params - } - }) - $ExoLogs.Add(@{ - message = "Removed $Username from $GroupName" - target = $UserID - groupName = $GroupName - }) } } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -headers $Headers -API $APIName -message "Error preparing bulk group removal requests: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Error preparing bulk group removal requests: $($ErrorMessage.NormalizedError)" } # Execute Graph bulk requests @@ -124,8 +133,7 @@ function Remove-CIPPGroups { } } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $Headers -API $APIName -message "Error executing Graph bulk requests: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - $Results.Add("Error executing bulk removal requests: $($ErrorMessage.NormalizedError)") + Write-Information "Error executing bulk Graph requests: $($ErrorMessage | ConvertTo-Json -Depth 5)" } } diff --git a/Modules/CIPPCore/Public/Remove-CIPPMailboxPermissions.ps1 b/Modules/CIPPCore/Public/Remove-CIPPMailboxPermissions.ps1 index 0b8927896be9..b3bbee435102 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPMailboxPermissions.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPMailboxPermissions.ps1 @@ -43,7 +43,7 @@ function Remove-CIPPMailboxPermissions { } } catch { $ErrorMsg = "Failed to remove permissions from $MailboxUPN for $AccessUser : $($_.Exception.Message)" - Write-LogMessage -headers $Headers -API $APIName -message $ErrorMsg -Sev 'Warning' -tenant $TenantFilter + Write-LogMessage -headers $Headers -API $APIName -message $ErrorMsg -sev 'Warn' -tenant $TenantFilter $Results.Add($ErrorMsg) } } diff --git a/Modules/CIPPCore/Public/Remove-CIPPMailboxRule.ps1 b/Modules/CIPPCore/Public/Remove-CIPPMailboxRule.ps1 index 1f470021a92b..9559f97eb0a7 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPMailboxRule.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPMailboxRule.ps1 @@ -46,7 +46,7 @@ function Remove-CIPPMailboxRule { try { Remove-CIPPDbItem -TenantFilter $TenantFilter -Type 'MailboxRules' -ItemId $RuleId } catch { - Write-LogMessage -headers $Headers -API $APIName -message "Rule deleted but failed to remove from cache: $($_.Exception.Message)" -Sev 'Warning' -tenant $TenantFilter + Write-LogMessage -headers $Headers -API $APIName -message "Rule deleted but failed to remove from cache: $($_.Exception.Message)" -sev 'Warn' -tenant $TenantFilter } return $Message diff --git a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 index eadfeef81f39..680ac28c9adf 100644 --- a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 @@ -31,7 +31,7 @@ function Set-CIPPAssignedPolicy { Write-Host "Found assignment filter: $($MatchingFilter.displayName) with ID: $ResolvedFilterId" } else { $ErrorMessage = "No assignment filter found matching the name: $AssignmentFilterName. Policy assigned without filter." - Write-LogMessage -headers $Headers -API $APIName -message $ErrorMessage -Sev 'Warning' -tenant $TenantFilter + Write-LogMessage -headers $Headers -API $APIName -message $ErrorMessage -sev 'Warn' -tenant $TenantFilter Write-Host $ErrorMessage } } @@ -95,7 +95,7 @@ function Set-CIPPAssignedPolicy { if (-not $resolvedGroupIds -or $resolvedGroupIds.Count -eq 0) { $ErrorMessage = "No groups found matching the specified name(s): $GroupName. Policy not assigned." - Write-LogMessage -headers $Headers -API $APIName -message $ErrorMessage -Sev 'Warning' -tenant $TenantFilter + Write-LogMessage -headers $Headers -API $APIName -message $ErrorMessage -sev 'Warn' -tenant $TenantFilter throw $ErrorMessage } diff --git a/Modules/CIPPCore/Public/Set-CIPPMailboxRule.ps1 b/Modules/CIPPCore/Public/Set-CIPPMailboxRule.ps1 index e99ead09a19d..06dc1d126e59 100644 --- a/Modules/CIPPCore/Public/Set-CIPPMailboxRule.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPMailboxRule.ps1 @@ -32,7 +32,7 @@ Enabled = $EnabledValue } } catch { - Write-LogMessage -headers $Headers -API $APIName -message "Rule updated but failed to update cache: $($_.Exception.Message)" -Sev 'Warning' -tenant $TenantFilter + Write-LogMessage -headers $Headers -API $APIName -message "Rule updated but failed to update cache: $($_.Exception.Message)" -sev 'Warn' -tenant $TenantFilter } return "Successfully set mailbox rule $($RuleName) for $($Username) to $($State)d" diff --git a/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 b/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 index 1285bbf1f402..0513553aaa3c 100644 --- a/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 @@ -30,7 +30,7 @@ function Set-CIPPResetPassword { } } catch { - Write-LogMessage -headers $Headers -API $APIName -message "Failed to create PwPush link, using plain password. Error: $($_.Exception.Message)" -Sev 'Warning' -tenant $TenantFilter + Write-LogMessage -headers $Headers -API $APIName -message "Failed to create PwPush link, using plain password. Error: $($_.Exception.Message)" -sev 'Warn' -tenant $TenantFilter } Write-LogMessage -headers $Headers -API $APIName -message "Successfully reset the password for $DisplayName, $($UserID). User must change password is set to $forceChangePasswordNextSignIn" -Sev 'Info' -tenant $TenantFilter diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 index 51dda90f7ff4..271ce8a186b0 100644 --- a/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 @@ -135,7 +135,7 @@ function Test-CIPPAccessTenant { Write-Warning "Found $($MissingRoles.Count) missing Organization Management roles in Exchange" $ExchangeStatus = $false $ExchangeTest = 'Connected to Exchange but missing permissions in Organization Management. This may impact the ability to manage Exchange features' - Write-LogMessage -headers $Headers -API $APINAME -tenant $tenant.defaultDomainName -message 'Tenant access check for Exchange failed: Missing Organization Management roles' -Sev 'Warning' -LogData $MissingOrgMgmtRoles + Write-LogMessage -headers $Headers -API $APINAME -tenant $tenant.defaultDomainName -message 'Tenant access check for Exchange failed: Missing Organization Management roles' -sev 'Warn' -LogData $MissingOrgMgmtRoles } else { Write-Warning 'All available Organization Management roles are present in Exchange' $ExchangeStatus = $true diff --git a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPGraphWebhookRenewal.ps1 b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPGraphWebhookRenewal.ps1 index 3bc4693fe6ed..9dd938033660 100644 --- a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPGraphWebhookRenewal.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPGraphWebhookRenewal.ps1 @@ -19,7 +19,7 @@ function Invoke-CippGraphWebhookRenewal { try { $TenantFilter = $UpdateSub.PartitionKey if ($Tenants.defaultDomainName -notcontains $TenantFilter -and $Tenants.customerId -notcontains $TenantFilter) { - Write-LogMessage -API 'Renew_Graph_Subscriptions' -message "Removing Subscription Renewal for $($UpdateSub.SubscriptionID) as tenant $TenantFilter is not in the tenant list." -Sev 'Warning' -tenant $TenantFilter + Write-LogMessage -API 'Renew_Graph_Subscriptions' -message "Removing Subscription Renewal for $($UpdateSub.SubscriptionID) as tenant $TenantFilter is not in the tenant list." -sev 'Warn' -tenant $TenantFilter Remove-AzDataTableEntity -Force @WebhookTable -Entity $UpdateSub continue } diff --git a/Modules/CippExtensions/Public/PwPush/New-PwPushLink.ps1 b/Modules/CippExtensions/Public/PwPush/New-PwPushLink.ps1 index ad58593c9676..1991b90598ae 100644 --- a/Modules/CippExtensions/Public/PwPush/New-PwPushLink.ps1 +++ b/Modules/CippExtensions/Public/PwPush/New-PwPushLink.ps1 @@ -55,7 +55,7 @@ function New-PwPushLink { 'Exception' = Get-CippException -Exception $_ } Write-LogMessage -API PwPush -Message "Failed to create a new PwPush link: $($_.Exception.Message)" -Sev 'Error' -LogData $LogData - Write-LogMessage -API PwPush -Message "Continuing without PwPush link due to error" -Sev 'Warning' + Write-LogMessage -API PwPush -Message "Continuing without PwPush link due to error" -sev 'Warn' return $false } } catch { From fdc5462252f492620db6abe3686261f0c513abf2 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 23 Feb 2026 00:11:46 -0500 Subject: [PATCH 11/22] Add Invoke-ListDBCache HTTP entrypoint Introduce Invoke-ListDBCache PowerShell function as an HTTP entrypoint for listing DB cache entries. Validates required query params (tenantFilter and type), returns 400 BadRequest when tenantFilter is missing, and when type is missing returns 400 with an AvailableTypes list derived from Get-CIPPDbItem -CountsOnly. When tenant exists, issues a New-CIPPDbRequest for the given tenantFilter and type and returns results in a 200 OK HttpResponseContext. --- .../HTTP Functions/Invoke-ListDBCache.ps1 | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Invoke-ListDBCache.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Invoke-ListDBCache.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Invoke-ListDBCache.ps1 new file mode 100644 index 000000000000..76268bd14dcb --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Invoke-ListDBCache.ps1 @@ -0,0 +1,46 @@ +function Invoke-ListDBCache { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + CIPP.Core.Read + #> + [CmdletBinding()] + param ( + $Request, + $TriggerMetadata + ) + + $TenantFilter = $Request.Query.tenantFilter + $Type = $Request.Query.type + + if (-not $TenantFilter) { + return ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = 'Error: tenantFilter query parameter is required' } + }) + } + + if (-not $Type) { + $Types = Get-CIPPDbItem -CountsOnly -TenantFilter $TenantFilter | Select-Object -ExpandProperty RowKey + $Types = $Types | ForEach-Object { $_ -replace '-Count$', '' } | Sort-Object + + return ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ + Results = 'Error: type query parameter is required' + AvailableTypes = $Types + } + }) + } + + $Tenant = Get-Tenants -TenantFilter $TenantFilter + if ($Tenant) { + $Results = New-CIPPDbRequest -TenantFilter $TenantFilter -Type $Type + } + + return ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @{ Results = $Results } + }) +} From 641a37ad00345d96121dd4689d514d8da421ea9e Mon Sep 17 00:00:00 2001 From: James Tarran Date: Mon, 23 Feb 2026 12:26:24 +0000 Subject: [PATCH 12/22] Update Invoke-CIPPStandardUserSubmissions.ps1 Previously, $StateIsCorrect only checked a subset of policy fields, while drift detection compared all fields. This caused perpetual drift reports for states that remediation considered correct and would never act on. Changes: - Expanded $StateIsCorrect checks to include address array counts (.Count -eq 0) and rule state, fully matching drift detection fields - Use Remove-ReportSubmissionRule to clear the rule - Wrap Exchange address collection properties with @() in $CurrentValue to normalise the Exchange MultiValuedProperty type for consistent JSON serialisation - Fix $ExpectedValue address fields: inline `if { @() }` returns $null (empty arrays write nothing to the pipeline); use @(if { ... }) outer wrapping instead to correctly produce an empty array - Fix $ExpectedValue.RuleState condition to include $state -eq 'disable', preventing an incorrect State='Enabled' expectation when email is configured but the standard is set to disable - Normalise RuleState.State to 'Disabled' and SentTo to $null in $CurrentValue when no submission rule exists --- .../Invoke-CIPPStandardUserSubmissions.ps1 | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 index 265959e7ce6a..6a949fad057f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 @@ -70,8 +70,11 @@ function Invoke-CIPPStandardUserSubmissions { $PolicyIsCorrect = ($PolicyState.EnableReportToMicrosoft -eq $true) -and ($PolicyState.ReportJunkToCustomizedAddress -eq $false) -and ($PolicyState.ReportNotJunkToCustomizedAddress -eq $false) -and - ($PolicyState.ReportPhishToCustomizedAddress -eq $false) - $RuleIsCorrect = $true + ($PolicyState.ReportPhishToCustomizedAddress -eq $false) -and + ($PolicyState.ReportJunkAddresses.Count -eq 0) -and + ($PolicyState.ReportNotJunkAddresses.Count -eq 0) -and + ($PolicyState.ReportPhishAddresses.Count -eq 0) + $RuleIsCorrect = ($RuleState.length -eq 0) -or ($RuleState.State -ne 'Enabled') } else { $PolicyIsCorrect = ($PolicyState.EnableReportToMicrosoft -eq $true) -and ($PolicyState.ReportJunkToCustomizedAddress -eq $true) -and @@ -91,8 +94,11 @@ function Invoke-CIPPStandardUserSubmissions { $PolicyIsCorrect = ($PolicyState.EnableReportToMicrosoft -eq $false) -and ($PolicyState.ReportJunkToCustomizedAddress -eq $false) -and ($PolicyState.ReportNotJunkToCustomizedAddress -eq $false) -and - ($PolicyState.ReportPhishToCustomizedAddress -eq $false) - $RuleIsCorrect = $true + ($PolicyState.ReportPhishToCustomizedAddress -eq $false) -and + ($PolicyState.ReportJunkAddresses.Count -eq 0) -and + ($PolicyState.ReportNotJunkAddresses.Count -eq 0) -and + ($PolicyState.ReportPhishAddresses.Count -eq 0) + $RuleIsCorrect = ($RuleState.length -eq 0) -or ($RuleState.State -ne 'Enabled') } } @@ -132,8 +138,11 @@ function Invoke-CIPPStandardUserSubmissions { $PolicyParams = @{ EnableReportToMicrosoft = $false ReportJunkToCustomizedAddress = $false + ReportJunkAddresses = $null ReportNotJunkToCustomizedAddress = $false + ReportNotJunkAddresses = $null ReportPhishToCustomizedAddress = $false + ReportPhishAddresses = $null } } @@ -177,6 +186,14 @@ function Invoke-CIPPStandardUserSubmissions { Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to enable User Submission rule. Error: $($ErrorMessage.NormalizedError)" -sev Error } } + } elseif ($RuleState.length -gt 0 -and $RuleState.State -eq 'Enabled') { + try { + $null = New-ExoRequest -tenantid $Tenant -cmdlet 'Remove-ReportSubmissionRule' -cmdParams @{ Identity = 'DefaultReportSubmissionRule' } -UseSystemMailbox $true + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'User Submission rule removed.' -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to remove User Submission rule. Error: $($ErrorMessage.NormalizedError)" -sev Error + } } } } @@ -211,12 +228,12 @@ function Invoke-CIPPStandardUserSubmissions { ReportJunkToCustomizedAddress = $PolicyState.ReportJunkToCustomizedAddress ReportNotJunkToCustomizedAddress = $PolicyState.ReportNotJunkToCustomizedAddress ReportPhishToCustomizedAddress = $PolicyState.ReportPhishToCustomizedAddress - ReportJunkAddresses = $PolicyState.ReportJunkAddresses - ReportNotJunkAddresses = $PolicyState.ReportNotJunkAddresses - ReportPhishAddresses = $PolicyState.ReportPhishAddresses + ReportJunkAddresses = @($PolicyState.ReportJunkAddresses) + ReportNotJunkAddresses = @($PolicyState.ReportNotJunkAddresses) + ReportPhishAddresses = @($PolicyState.ReportPhishAddresses) RuleState = @{ - State = $RuleState.State - SentTo = $RuleState.SentTo + State = if ($RuleState.length -eq 0) { 'Disabled' } else { $RuleState.State } + SentTo = if ($RuleState.length -eq 0) { $null } else { @($RuleState.SentTo) } } } $ExpectedValue = @{ @@ -224,10 +241,10 @@ function Invoke-CIPPStandardUserSubmissions { ReportJunkToCustomizedAddress = if ([string]::IsNullOrWhiteSpace($Email)) { $false } else { $true } ReportNotJunkToCustomizedAddress = if ([string]::IsNullOrWhiteSpace($Email)) { $false } else { $true } ReportPhishToCustomizedAddress = if ([string]::IsNullOrWhiteSpace($Email)) { $false } else { $true } - ReportJunkAddresses = if ([string]::IsNullOrWhiteSpace($Email)) { $null } else { @($Email) } - ReportNotJunkAddresses = if ([string]::IsNullOrWhiteSpace($Email)) { $null } else { @($Email) } - ReportPhishAddresses = if ([string]::IsNullOrWhiteSpace($Email)) { $null } else { @($Email) } - RuleState = if ([string]::IsNullOrWhiteSpace($Email)) { + ReportJunkAddresses = @(if (-not [string]::IsNullOrWhiteSpace($Email)) { $Email }) + ReportNotJunkAddresses = @(if (-not [string]::IsNullOrWhiteSpace($Email)) { $Email }) + ReportPhishAddresses = @(if (-not [string]::IsNullOrWhiteSpace($Email)) { $Email }) + RuleState = if ([string]::IsNullOrWhiteSpace($Email) -or $state -eq 'disable') { @{ State = 'Disabled' SentTo = $null From fa426208d5d045fa4462d0969cf6af2464783430 Mon Sep 17 00:00:00 2001 From: rvdwegen Date: Mon, 23 Feb 2026 15:29:48 +0100 Subject: [PATCH 13/22] Better tenant lookup --- .../Entrypoints/Invoke-ListExternalTenantInfo.ps1 | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListExternalTenantInfo.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListExternalTenantInfo.ps1 index 1bf2c4bfaabc..e1fe3dc11a16 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListExternalTenantInfo.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListExternalTenantInfo.ps1 @@ -22,10 +22,24 @@ function Invoke-ListExternalTenantInfo { if ($TenantId) { $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/findTenantInformationByTenantId(tenantId='$TenantId')" -NoAuthCheck $true -tenantid $env:TenantID + + + # New API call to retrieve branding details + $brandingBody = @{ + username = "completelymadeupdoesnthavetobevalid@$($GraphRequest.defaultDomainName)" + } | ConvertTo-Json + + $brandingHeaders = @{ + "Content-Type" = "application/json" + } + + $brandingResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/common/GetCredentialType" -Body $brandingBody -Headers $brandingHeaders + $StatusCode = [HttpStatusCode]::OK $HttpResponse.Body = [PSCustomObject]@{ GraphRequest = $GraphRequest OpenIdConfig = $OpenIdConfig + UserTenantBranding = $brandingResponse.EstsProperties.UserTenantBranding } } else { $HttpResponse.StatusCode = [HttpStatusCode]::BadRequest From 362cdacc7c3c25d94e6872407a130781d39bf938 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:23:56 +0100 Subject: [PATCH 14/22] removed for prettiness sake --- Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 b/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 index a2a6553fb250..b0eac304e54c 100644 --- a/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 +++ b/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 @@ -128,8 +128,8 @@ function Get-CIPPTenantAlignment { $TenantValues.Add($filterItem.value) } } -` - if ($TenantValues -contains 'AllTenants') { + + if ($TenantValues -contains 'AllTenants') { $AppliestoAllTenants = $true } elseif ($TenantValues.Count -gt 0) { $TemplateAssignedTenants = @($TenantValues) From 6c38c16154e42a96533cfc0c2897512dee67d838 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:19:37 +0800 Subject: [PATCH 15/22] Add device local admin standard function Add Invoke-CIPPStandardintuneDeviceRegLocalAdmins. Control whether users who register/enroll devices are granted local admin rights and whether Global Administrators are added as local admins. --- ...CIPPStandardintuneDeviceRegLocalAdmins.ps1 | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRegLocalAdmins.ps1 diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRegLocalAdmins.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRegLocalAdmins.ps1 new file mode 100644 index 000000000000..178db7c71abf --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRegLocalAdmins.ps1 @@ -0,0 +1,101 @@ +function Invoke-CIPPStandardintuneDeviceRegLocalAdmins { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) intuneDeviceRegLocalAdmins + .SYNOPSIS + (Label) Configure local administrator rights for users joining devices + .DESCRIPTION + (Helptext) Controls whether users who register Microsoft Entra joined devices are granted local administrator rights on those devices and if Global Administrators are added as local admins. + (DocsDescription) Configures the Device Registration Policy local administrator behavior for registering users. When enabled, users who register devices are not granted local administrator rights, you can also configure if Global Administrators are added as local admins. + .NOTES + CAT + Entra (AAD) Standards + TAG + EXECUTIVETEXT + Controls whether employees who enroll devices automatically receive local administrator access. Disabling registering-user admin rights follows least-privilege principles and reduces security risk from over-privileged endpoints. + ADDEDCOMPONENT + {"type":"switch","name":"standards.intuneDeviceRegLocalAdmins.disableRegisteringUsers","label":"Disable registering users as local administrators","defaultValue":true} + {"type":"switch","name":"standards.intuneDeviceRegLocalAdmins.enableGlobalAdmins","label":"Allow Global Administrators to be local administrators","defaultValue":true} + IMPACT + Medium Impact + ADDEDDATE + 2026-02-23 + POWERSHELLEQUIVALENT + Update-MgBetaPolicyDeviceRegistrationPolicy + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/list-standards + #> + + param($Tenant, $Settings) + + try { + $PreviousSetting = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy' -tenantid $Tenant + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the intuneDeviceRegLocalAdmins state for $Tenant. Error: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage + return + } + # Current M365 Config + $CurrentOdataType = $PreviousSetting.azureADJoin.localAdmins.registeringUsers.'@odata.type' + $CurrentEnableGlobalAdmins = [bool]$PreviousSetting.azureADJoin.localAdmins.enableGlobalAdmins + + # Standards Config + $DisableRegisteringUsers = [bool]$Settings.disableRegisteringUsers + $EnableGlobalAdmins = [bool]$Settings.enableGlobalAdmins + + # State comparison + $DesiredOdataType = if ($DisableRegisteringUsers) { '#microsoft.graph.noDeviceRegistrationMembership' } else { '#microsoft.graph.allDeviceRegistrationMembership' } + $StateIsCorrect = ($CurrentOdataType -eq $DesiredOdataType) -and ($CurrentEnableGlobalAdmins -eq $EnableGlobalAdmins) + $DesiredStateText = if ($DisableRegisteringUsers) { 'disabled' } else { 'enabled' } + $DesiredGlobalAdminsText = if ($EnableGlobalAdmins) { 'enabled' } else { 'disabled' } + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Local administrator settings are already configured (registering users: $DesiredStateText, global admins: $DesiredGlobalAdminsText)." -sev Info + } else { + try { + $PreviousSetting.azureADJoin.localAdmins.registeringUsers = @{ '@odata.type' = $DesiredOdataType } + $PreviousSetting.azureADJoin.localAdmins.enableGlobalAdmins = $EnableGlobalAdmins + $NewBody = ConvertTo-Json -Compress -InputObject $PreviousSetting -Depth 10 + New-GraphPostRequest -tenantid $Tenant -Uri 'https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy' -Type PUT -Body $NewBody -ContentType 'application/json' + $CurrentOdataType = $DesiredOdataType + $CurrentEnableGlobalAdmins = $EnableGlobalAdmins + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Set local administrator settings (registering users: $DesiredStateText, global admins: $DesiredGlobalAdminsText)." -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set local administrator settings (registering users: $DesiredStateText, global admins: $DesiredGlobalAdminsText). Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Local administrator settings are configured as expected (registering users: $DesiredStateText, global admins: $DesiredGlobalAdminsText)." -sev Info + } else { + Write-StandardsAlert -message "Local administrator settings are not configured as expected (registering users: $DesiredStateText, global admins: $DesiredGlobalAdminsText)" -object @{ current = @{ registeringUsers = $CurrentOdataType; enableGlobalAdmins = $CurrentEnableGlobalAdmins }; desired = @{ registeringUsers = $DesiredOdataType; enableGlobalAdmins = $EnableGlobalAdmins } } -tenant $Tenant -standardName 'intuneDeviceRegLocalAdmins' -standardId $Settings.standardId + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Local administrator settings are not configured as expected (registering users: $DesiredStateText, global admins: $DesiredGlobalAdminsText)." -sev Info + } + } + + if ($Settings.report -eq $true) { + $CurrentValue = @{ + registeringUsers = @{ + '@odata.type' = $CurrentOdataType + } + enableGlobalAdmins = $CurrentEnableGlobalAdmins + } + $ExpectedValue = @{ + registeringUsers = @{ + '@odata.type' = $DesiredOdataType + } + enableGlobalAdmins = $EnableGlobalAdmins + } + Set-CIPPStandardsCompareField -FieldName 'standards.intuneDeviceRegLocalAdmins' -CurrentValue $CurrentValue -ExpectedValue $ExpectedValue -TenantFilter $Tenant + Add-CIPPBPAField -FieldName 'intuneDeviceRegLocalAdmins' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} From 7baeb92e0cac454287ff75f9e6efc0f66d036845 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:21:58 +0100 Subject: [PATCH 16/22] fixes setup wizard to allow temproary headers. --- .../CIPP/Setup/Invoke-ExecCreateSAMApp.ps1 | 3 ++- .../GraphHelper/New-GraphBulkRequest.ps1 | 13 +++++++++---- .../GraphHelper/New-GraphGetRequest.ps1 | 14 +++++++++----- .../GraphHelper/New-GraphPOSTRequest.ps1 | 13 +++++++++---- .../Update-AppManagementPolicy.ps1 | 19 +++++++++---------- 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecCreateSAMApp.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecCreateSAMApp.ps1 index 202d6e4dc182..29cc2abb5221 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecCreateSAMApp.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecCreateSAMApp.ps1 @@ -70,7 +70,8 @@ function Invoke-ExecCreateSAMApp { } try { - $AppPolicyStatus = Update-AppManagementPolicy + + $AppPolicyStatus = Update-AppManagementPolicy -Headers @{ authorization = "Bearer $($Token.access_token)" } -ApplicationId $appId.appId Write-Information $AppPolicyStatus.PolicyAction } catch { Write-Warning "Error updating app management policy $($_.Exception.Message)." diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 index 0b961c534385..2cfabcdc42f2 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 @@ -12,11 +12,16 @@ function New-GraphBulkRequest { $Requests, $NoPaginateIds = @(), [ValidateSet('v1.0', 'beta')] - $Version = 'beta' + $Version = 'beta', + $Headers ) if ($NoAuthCheck -or (Get-AuthorisedRequest -Uri $uri -TenantID $tenantid)) { - $headers = Get-GraphToken -tenantid $tenantid -scope $scope -AsApp $asapp + if ($Headers) { + $Headers = $Headers + } else { + $Headers = Get-GraphToken -tenantid $tenantid -scope $scope -AsApp $asapp + } if ($script:XMsThrottlePriority) { $headers['x-ms-throttle-priority'] = $script:XMsThrottlePriority @@ -56,13 +61,14 @@ function New-GraphBulkRequest { } Write-Host 'Getting more' Write-Host $MoreData.body.'@odata.nextLink' - $AdditionalValues = New-GraphGetRequest -ComplexFilter -uri $MoreData.body.'@odata.nextLink' -tenantid $tenantid -NoAuthCheck $NoAuthCheck -scope $scope -AsApp $asapp + $AdditionalValues = New-GraphGetRequest -ComplexFilter -uri $MoreData.body.'@odata.nextLink' -tenantid $tenantid -NoAuthCheck $NoAuthCheck -scope $scope -AsApp $asapp -headers $Headers $NewValues = [System.Collections.Generic.List[PSCustomObject]]$MoreData.body.value $AdditionalValues | ForEach-Object { $NewValues.add($_) } $MoreData.body.value = $NewValues } } catch { + Write-Host 'updating graph table because something failed.' # Try to parse ErrorDetails.Message as JSON if ($_.ErrorDetails.Message) { try { @@ -91,7 +97,6 @@ function New-GraphBulkRequest { $Tenant.LastGraphError = '' } Update-AzDataTableEntity -Force @TenantsTable -Entity $Tenant - return $ReturnedData.responses } else { Write-Error 'Not allowed. You cannot manage your own tenant or tenants not under your scope' diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 index 23b0e59d3dc7..be328974d1e2 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 @@ -17,7 +17,8 @@ function New-GraphGetRequest { [switch]$CountOnly, [switch]$IncludeResponseHeaders, [hashtable]$extraHeaders, - [switch]$ReturnRawResponse + [switch]$ReturnRawResponse, + $Headers ) if ($NoAuthCheck -eq $false) { @@ -27,12 +28,15 @@ function New-GraphGetRequest { } if ($NoAuthCheck -eq $true -or $IsAuthorised) { - if ($scope -eq 'ExchangeOnline') { - $headers = Get-GraphToken -tenantid $tenantid -scope 'https://outlook.office365.com/.default' -AsApp $asapp -SkipCache $skipTokenCache + if ($headers) { + $headers = $Headers } else { - $headers = Get-GraphToken -tenantid $tenantid -scope $scope -AsApp $asapp -SkipCache $skipTokenCache + if ($scope -eq 'ExchangeOnline') { + $headers = Get-GraphToken -tenantid $tenantid -scope 'https://outlook.office365.com/.default' -AsApp $asapp -SkipCache $skipTokenCache + } else { + $headers = Get-GraphToken -tenantid $tenantid -scope $scope -AsApp $asapp -SkipCache $skipTokenCache + } } - if ($ComplexFilter) { $headers['ConsistencyLevel'] = 'eventual' } diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 index d2ac97fb8839..d637e0d7c6c8 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 @@ -18,11 +18,16 @@ function New-GraphPOSTRequest { $IgnoreErrors = $false, $returnHeaders = $false, $maxRetries = 3, - $ScheduleRetry = $false + $ScheduleRetry = $false, + $headers ) if ($NoAuthCheck -or (Get-AuthorisedRequest -Uri $uri -TenantID $tenantid)) { - $headers = Get-GraphToken -tenantid $tenantid -scope $scope -AsApp $asapp -SkipCache $skipTokenCache + if ($Headers) { + $Headers = $Headers + } else { + $Headers = Get-GraphToken -tenantid $tenantid -scope $scope -AsApp $asapp -SkipCache $skipTokenCache + } if ($AddedHeaders) { foreach ($header in $AddedHeaders.GetEnumerator()) { $headers.Add($header.Key, $header.Value) @@ -36,8 +41,8 @@ function New-GraphPOSTRequest { if (!$contentType) { $contentType = 'application/json; charset=utf-8' } - - $body = Get-CIPPTextReplacement -TenantFilter $tenantid -Text $body -EscapeForJson + #Only do text replacement if no headers are set. + if (!$headers) { $body = Get-CIPPTextReplacement -TenantFilter $tenantid -Text $body -EscapeForJson } $RetryCount = 0 $RequestSuccessful = $false diff --git a/Modules/CIPPCore/Public/GraphHelper/Update-AppManagementPolicy.ps1 b/Modules/CIPPCore/Public/GraphHelper/Update-AppManagementPolicy.ps1 index 1c5e20ae81df..5a44d65fddd5 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Update-AppManagementPolicy.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Update-AppManagementPolicy.ps1 @@ -15,7 +15,8 @@ function Update-AppManagementPolicy { [CmdletBinding()] param( $TenantFilter = $env:TenantID, - $ApplicationId = $env:ApplicationID + $ApplicationId = $env:ApplicationID, + $headers ) try { @@ -39,8 +40,7 @@ function Update-AppManagementPolicy { ) # Execute bulk request - $Results = New-GraphBulkRequest -Requests $Requests -NoAuthCheck $true -asapp $true -tenantid $TenantFilter - + $Results = New-GraphBulkRequest -Requests $Requests -NoAuthCheck $true -asapp $true -tenantid $TenantFilter -headers $headers # Parse results $DefaultPolicy = ($Results | Where-Object { $_.id -eq 'defaultPolicy' }).body $AppPolicies = ($Results | Where-Object { $_.id -eq 'appPolicies' }).body.value @@ -60,8 +60,7 @@ function Update-AppManagementPolicy { }) if ($AppliesToRequests.Count -gt 0) { - $AppliesToResults = New-GraphBulkRequest -Requests $AppliesToRequests -NoAuthCheck $true -asapp $true -tenantid $TenantFilter - + $AppliesToResults = New-GraphBulkRequest -Requests $AppliesToRequests -NoAuthCheck $true -asapp $true -tenantid $TenantFilter -headers $headers # Find which policy (if any) targets the app $CIPPPolicyResult = $AppliesToResults | Where-Object { $_.body.value.appId -contains $ApplicationId } | Select-Object -First 1 if ($CIPPPolicyResult) { @@ -171,18 +170,18 @@ function Update-AppManagementPolicy { if ($CIPPAppPolicyId) { # Update existing policy that's already assigned to the app - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/policies/appManagementPolicies/$CIPPAppPolicyId" -type PATCH -body ($PolicyBody | ConvertTo-Json -Depth 10) -asapp $true -NoAuthCheck $true -tenantid $TenantFilter + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/policies/appManagementPolicies/$CIPPAppPolicyId" -type PATCH -body ($PolicyBody | ConvertTo-Json -Depth 10) -asapp $true -NoAuthCheck $true -tenantid $TenantFilter -headers $headers $PolicyAction = "Updated existing policy $CIPPAppPolicyId to allow credentials" } elseif ($ExistingExemptionPolicy) { # Exemption policy exists but not assigned to app - update and assign it - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/policies/appManagementPolicies/$($ExistingExemptionPolicy.id)" -type PATCH -body ($PolicyBody | ConvertTo-Json -Depth 10) -asapp $true -NoAuthCheck $true + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/policies/appManagementPolicies/$($ExistingExemptionPolicy.id)" -type PATCH -body ($PolicyBody | ConvertTo-Json -Depth 10) -asapp $true -NoAuthCheck $true -headers $headers if ($CIPPApp.id) { # Assign existing policy to CIPP-SAM application $AssignBody = @{ '@odata.id' = "https://graph.microsoft.com/beta/policies/appManagementPolicies/$($ExistingExemptionPolicy.id)" } - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/applications/$($CIPPApp.id)/appManagementPolicies/`$ref" -type POST -body ($AssignBody | ConvertTo-Json) -asapp $true -NoAuthCheck $true -tenantid $TenantFilter + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/applications/$($CIPPApp.id)/appManagementPolicies/`$ref" -type POST -body ($AssignBody | ConvertTo-Json) -asapp $true -NoAuthCheck $true -tenantid $TenantFilter -headers $headers $PolicyAction = "Updated and assigned existing policy $($ExistingExemptionPolicy.id) to CIPP-SAM" $CIPPAppPolicyId = $ExistingExemptionPolicy.id $CIPPAppTargeted = $true @@ -191,14 +190,14 @@ function Update-AppManagementPolicy { } } else { # Create new policy and assign to CIPP-SAM app - $CreatedPolicy = New-GraphPostRequest -uri 'https://graph.microsoft.com/v1.0/policies/appManagementPolicies' -type POST -body ($PolicyBody | ConvertTo-Json -Depth 10) -asapp $true -NoAuthCheck $true + $CreatedPolicy = New-GraphPostRequest -uri 'https://graph.microsoft.com/v1.0/policies/appManagementPolicies' -type POST -body ($PolicyBody | ConvertTo-Json -Depth 10) -asapp $true -NoAuthCheck $true -headers $headers if ($CIPPApp.id) { # Assign policy to CIPP-SAM application using beta endpoint $AssignBody = @{ '@odata.id' = "https://graph.microsoft.com/beta/policies/appManagementPolicies/$($CreatedPolicy.id)" } - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/applications/$($CIPPApp.id)/appManagementPolicies/`$ref" -type POST -body ($AssignBody | ConvertTo-Json) -asapp $true -NoAuthCheck $true + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/applications/$($CIPPApp.id)/appManagementPolicies/`$ref" -type POST -body ($AssignBody | ConvertTo-Json) -asapp $true -NoAuthCheck $true -headers $headers $PolicyAction = "Created new policy $($CreatedPolicy.id) and assigned to CIPP-SAM" $CIPPAppPolicyId = $CreatedPolicy.id $CIPPAppTargeted = $true From c6d1a49a44c3de2ade590b6f27396e5465c67b1a Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:47:11 +0100 Subject: [PATCH 17/22] remove secret logging line for local dev --- .../HTTP Functions/CIPP/Setup/Invoke-ExecCreateSAMApp.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecCreateSAMApp.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecCreateSAMApp.ps1 index 29cc2abb5221..f7ca33f56c56 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecCreateSAMApp.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecCreateSAMApp.ps1 @@ -89,10 +89,8 @@ function Invoke-ExecCreateSAMApp { $Secret | Add-Member -MemberType NoteProperty -Name 'tenantid' -Value $TenantId -Force $Secret | Add-Member -MemberType NoteProperty -Name 'applicationid' -Value $AppId.appId -Force $Secret | Add-Member -MemberType NoteProperty -Name 'applicationsecret' -Value $AppPassword -Force - Write-Information ($Secret | ConvertTo-Json -Depth 5) Add-CIPPAzDataTableEntity @DevSecretsTable -Entity $Secret -Force } else { - Set-CippKeyVaultSecret -VaultName $kv -Name 'tenantid' -SecretValue (ConvertTo-SecureString -String $TenantId -AsPlainText -Force) Set-CippKeyVaultSecret -VaultName $kv -Name 'applicationid' -SecretValue (ConvertTo-SecureString -String $Appid.appId -AsPlainText -Force) Set-CippKeyVaultSecret -VaultName $kv -Name 'applicationsecret' -SecretValue (ConvertTo-SecureString -String $AppPassword -AsPlainText -Force) From 68bbf04a33ecc69540cff0ea6812a0de183494e8 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:06:46 +0100 Subject: [PATCH 18/22] fixed #5444 --- .../Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 index ad2c5d7c5466..74d66cea6d1a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 @@ -19,21 +19,21 @@ function Invoke-AddIntuneTemplate { $reusableTemplateRefs = @() $object = [PSCustomObject]@{ - Displayname = $Request.Body.displayName - Description = $Request.Body.description - RAWJson = $Request.Body.RawJSON - Type = $Request.Body.TemplateType - GUID = $GUID - ReusableSettings = $reusableTemplateRefs + Displayname = $Request.Body.displayName + Description = $Request.Body.description + RAWJson = $Request.Body.RawJSON + Type = $Request.Body.TemplateType + GUID = $GUID + ReusableSettings = $reusableTemplateRefs } | ConvertTo-Json $Table = Get-CippTable -tablename 'templates' $Table.Force = $true Add-CIPPAzDataTableEntity @Table -Entity @{ - JSON = "$object" + JSON = "$object" ReusableSettingsCount = $reusableTemplateRefs.Count - RowKey = "$GUID" - PartitionKey = 'IntuneTemplate' - GUID = "$GUID" + RowKey = "$GUID" + PartitionKey = 'IntuneTemplate' + GUID = "$GUID" } Write-LogMessage -headers $Headers -API $APIName -message "Created intune policy template named $($Request.Body.displayName) with GUID $GUID" -Sev 'Debug' @@ -56,8 +56,7 @@ function Invoke-AddIntuneTemplate { Type = $Template.Type GUID = $GUID ReusableSettings = $reusableTemplateRefs - } - + } | ConvertTo-Json -Compress $Table = Get-CippTable -tablename 'templates' $Table.Force = $true Add-CIPPAzDataTableEntity @Table -Entity @{ From 7f1ed6009f3df1e50de0d61ddfe40e0b42905758 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:13:00 +0100 Subject: [PATCH 19/22] contact emails --- .../Public/Standards/Invoke-CIPPStandardMailContacts.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 index e804e605ce61..b06b0567df97 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 @@ -106,12 +106,12 @@ function Invoke-CIPPStandardMailContacts { } if ($Settings.report -eq $true) { $CurrentValue = @{ - marketingNotificationEmails = $CurrentInfo.marketingNotificationEmails + marketingNotificationEmails = @($CurrentInfo.marketingNotificationEmails) technicalNotificationMails = @($CurrentInfo.technicalNotificationMails) contactEmail = $CurrentInfo.privacyProfile.contactEmail } $ExpectedValue = @{ - marketingNotificationEmails = $Contacts.MarketingContact + marketingNotificationEmails = @($Contacts.MarketingContact) technicalNotificationMails = @($Contacts.SecurityContact, $Contacts.TechContact) | Where-Object { $_ -ne $null } contactEmail = $Contacts.GeneralContact } From 4ff873286647b21f6b7656e84c1026f794c32464 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:27:51 +0100 Subject: [PATCH 20/22] maximum kilobytes of internet(fixes remove empty array and returns it to KelvinCode --- .../Public/Functions/Remove-EmptyArrays.ps1 | 39 ++----------------- Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 | 2 +- 2 files changed, 5 insertions(+), 36 deletions(-) diff --git a/Modules/CIPPCore/Public/Functions/Remove-EmptyArrays.ps1 b/Modules/CIPPCore/Public/Functions/Remove-EmptyArrays.ps1 index 85726be4607a..fd46b76e72b5 100644 --- a/Modules/CIPPCore/Public/Functions/Remove-EmptyArrays.ps1 +++ b/Modules/CIPPCore/Public/Functions/Remove-EmptyArrays.ps1 @@ -1,40 +1,11 @@ -function Remove-EmptyArrays { - <# - .SYNOPSIS - Recursively removes empty arrays and null properties from objects - .DESCRIPTION - This function recursively traverses an object (Array, Hashtable, or PSCustomObject) and removes: - - Empty arrays - - Null properties - The function modifies the object in place. - .PARAMETER Object - The object to process (can be Array, Hashtable, or PSCustomObject) - .FUNCTIONALITY - Internal - .EXAMPLE - $obj = @{ items = @(); name = "test"; value = $null } - Remove-EmptyArrays -Object $obj - .EXAMPLE - $obj = [PSCustomObject]@{ items = @(); name = "test" } - Remove-EmptyArrays -Object $obj - #> - [CmdletBinding()] - param( - [Parameter(Mandatory = $true)] - [object]$Object - ) - +function Remove-EmptyArrays ($Object) { if ($Object -is [Array]) { - foreach ($Item in $Object) { - Remove-EmptyArrays -Object $Item - } + foreach ($Item in $Object) { Remove-EmptyArrays $Item } } elseif ($Object -is [HashTable]) { foreach ($Key in @($Object.get_Keys())) { if ($Object[$Key] -is [Array] -and $Object[$Key].get_Count() -eq 0) { $Object.Remove($Key) - } else { - Remove-EmptyArrays -Object $Object[$Key] - } + } else { Remove-EmptyArrays $Object[$Key] } } } elseif ($Object -is [PSCustomObject]) { foreach ($Name in @($Object.PSObject.Properties.Name)) { @@ -42,9 +13,7 @@ function Remove-EmptyArrays { $Object.PSObject.Properties.Remove($Name) } elseif ($null -eq $Object.$Name) { $Object.PSObject.Properties.Remove($Name) - } else { - Remove-EmptyArrays -Object $Object.$Name - } + } else { Remove-EmptyArrays $Object.$Name } } } } diff --git a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 index 76fba584da6d..d637c614e88d 100644 --- a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 @@ -89,7 +89,7 @@ function New-CIPPCAPolicy { $displayName = ($RawJSON | ConvertFrom-Json).displayName $JSONobj = $RawJSON | ConvertFrom-Json | Select-Object * -ExcludeProperty ID, GUID, *time* - Remove-EmptyArrays -Object $JSONobj + Remove-EmptyArrays $JSONobj #Remove context as it does not belong in the payload. try { if ($JSONobj.grantControls) { From 733ca03dc70d7302848faee4fd5afb87f4b09dd6 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 24 Feb 2026 13:14:59 -0500 Subject: [PATCH 21/22] Add remediate/report to proper StandardTemplate object Fix in Invoke-ExecUpdateDriftDeviation.ps1: previously the 'remediate' and 'report' NoteProperties were being added to $StandardTemplate.standards.$Setting and $Settings was set to that nested object. This change adds the properties directly to $StandardTemplate and sets $Settings to $StandardTemplate, ensuring the correct object receives the flags and that downstream code receives the expected settings structure. --- .../Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1 index 90dcfc65b713..0d6b2b52c592 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1 @@ -58,9 +58,9 @@ function Invoke-ExecUpdateDriftDeviation { $Settings = $StandardTemplate } else { $StandardTemplate = $StandardTemplate.standardSettings.$Setting - $StandardTemplate.standards.$Setting | Add-Member -MemberType NoteProperty -Name 'remediate' -Value $true -Force - $StandardTemplate.standards.$Setting | Add-Member -MemberType NoteProperty -Name 'report' -Value $true -Force - $Settings = $StandardTemplate.standards.$Setting + $StandardTemplate | Add-Member -MemberType NoteProperty -Name 'remediate' -Value $true -Force + $StandardTemplate | Add-Member -MemberType NoteProperty -Name 'report' -Value $true -Force + $Settings = $StandardTemplate } $TaskBody = @{ TenantFilter = $TenantFilter From 7ec3326547393a39eca84373c31bf25c2ec1e1b6 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 24 Feb 2026 13:16:57 -0500 Subject: [PATCH 22/22] bump version --- host.json | 2 +- version_latest.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/host.json b/host.json index 2af179e475df..e215e52d2205 100644 --- a/host.json +++ b/host.json @@ -16,7 +16,7 @@ "distributedTracingEnabled": false, "version": "None" }, - "defaultVersion": "10.0.9", + "defaultVersion": "10.1.1", "versionMatchStrategy": "Strict", "versionFailureStrategy": "Fail" } diff --git a/version_latest.txt b/version_latest.txt index 4149c39eec6f..23127993ac05 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -10.1.0 +10.1.1