From 7264fdc8f4ce0dadcf12d63cf25ea02d6070f547 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 21 Nov 2025 12:53:16 -0500 Subject: [PATCH 01/15] fix caching issues --- .../Test-CIPPAccessUserRole.ps1 | 26 +++++++++++++------ profile.ps1 | 4 +++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccessUserRole.ps1 b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccessUserRole.ps1 index 5410d7312142..ec7bf596ca37 100644 --- a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccessUserRole.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccessUserRole.ps1 @@ -20,9 +20,15 @@ function Test-CIPPAccessUserRole { $User ) $Roles = @() - $Table = Get-CippTable -TableName cacheAccessUserRoles - $Filter = "PartitionKey eq 'AccessUser' and RowKey eq '$($User.userDetails)' and Timestamp ge datetime'$((Get-Date).AddMinutes(-15).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))'" - $UserRole = Get-CIPPAzDataTableEntity @Table -Filter $Filter + + try { + $Table = Get-CippTable -TableName cacheAccessUserRoles + $Filter = "PartitionKey eq 'AccessUser' and RowKey eq '$($User.userDetails)' and Timestamp ge datetime'$((Get-Date).AddMinutes(-15).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))'" + $UserRole = Get-CIPPAzDataTableEntity @Table -Filter $Filter + } catch { + Write-Information "Could not access cached user roles table. $($_.Exception.Message)" + $UserRole = $null + } if ($UserRole) { Write-Information "Found cached user role for $($User.userDetails)" $Roles = $UserRole.Role | ConvertFrom-Json @@ -59,12 +65,16 @@ function Test-CIPPAccessUserRole { } if (($Roles | Measure-Object).Count -gt 2) { - $UserRole = [PSCustomObject]@{ - PartitionKey = 'AccessUser' - RowKey = [string]$User.userDetails - Role = [string](ConvertTo-Json -Compress -InputObject $Roles) + try { + $UserRole = [PSCustomObject]@{ + PartitionKey = 'AccessUser' + RowKey = [string]$User.userDetails + Role = [string](ConvertTo-Json -Compress -InputObject $Roles) + } + Add-CIPPAzDataTableEntity @Table -Entity $UserRole -Force + } catch { + Write-Information "Could not cache user roles for $($User.userDetails). $($_.Exception.Message)" } - Add-CIPPAzDataTableEntity @Table -Entity $UserRole -Force } } $User.userRoles = $Roles diff --git a/profile.ps1 b/profile.ps1 index e8a94d1cea5e..97c133727559 100644 --- a/profile.ps1 +++ b/profile.ps1 @@ -58,6 +58,10 @@ if (!$LastStartup -or $CurrentVersion -ne $LastStartup.Version) { } catch { Write-LogMessage -message 'Failed to clear durables after update' -LogData (Get-CippException -Exception $_) -Sev 'Error' } + + $ReleaseTable = Get-CippTable -tablename 'cacheGitHubReleaseNotes' + Remove-AzDataTableEntity @ReleaseTable -Entity @{ PartitionKey = 'GitHubReleaseNotes'; RowKey = 'GitHubReleaseNotes' } -ErrorAction SilentlyContinue + Write-Host 'Cleared GitHub release notes cache to force refresh on version update.' } # Uncomment the next line to enable legacy AzureRm alias in Azure PowerShell. # Enable-AzureRmAlias From 8a7674899a1a34f0e42590fd3ac9f9aa4d2c9bc7 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 21 Nov 2025 22:22:48 -0500 Subject: [PATCH 02/15] Add error handling for tenant capabilities fetch Wrapped the retrieval of tenant capabilities and SKUId in a try-catch block to log errors and provide better diagnostics when fetching capabilities fails. Also added logging for errors when fetching tenant variables. --- .../TenantGroups/Update-CIPPDynamicTenantGroups.ps1 | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1 b/Modules/CIPPCore/Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1 index 41ec19b11af1..2ec937e023a3 100644 --- a/Modules/CIPPCore/Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1 +++ b/Modules/CIPPCore/Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1 @@ -173,11 +173,17 @@ function Update-CIPPDynamicTenantGroups { $TenantVariables = Get-CIPPTenantVariables -TenantFilter $_.customerId -IncludeGlobal } catch { Write-Information "Error fetching custom variables for tenant $($_.defaultDomainName): $($_.Exception.Message)" + Write-LogMessage -API 'TenantGroups' -message 'Error getting tenant variables' -Tenant $_.defaultDomainName -sev Warning -LogData (Get-CippException -Exception $_) } } - $SKUId = $LicenseInfo.SKUId ?? @() - $ServicePlans = (Get-CIPPTenantCapabilities -TenantFilter $_.defaultDomainName).psobject.properties.name + try { + $SKUId = $LicenseInfo.SKUId ?? @() + $ServicePlans = (Get-CIPPTenantCapabilities -TenantFilter $_.defaultDomainName).psobject.properties.name + } catch { + Write-Information "Error fetching capabilities for tenant $($_.defaultDomainName): $($_.Exception.Message)" + Write-LogMessage -API 'TenantGroups' -message 'Error getting tenant capabilities' -Tenant $_.defaultDomainName -sev Warning -LogData (Get-CippException -Exception $_) + } [pscustomobject]@{ customerId = $_.customerId defaultDomainName = $_.defaultDomainName From 72a696586bd60595ce01eeac216655411b4086db Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 21 Nov 2025 22:22:55 -0500 Subject: [PATCH 03/15] Add retry logic to New-GraphPOSTRequest Introduced a $maxRetries parameter and implemented retry logic with exponential backoff for failed requests in New-GraphPOSTRequest. This improves reliability when transient errors occur during REST API calls. --- .../GraphHelper/New-GraphPOSTRequest.ps1 | 54 +++++++++++++------ 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 index 5cff84ab5762..ebcabfa12702 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 @@ -1,9 +1,25 @@ -function New-GraphPOSTRequest ($uri, $tenantid, $body, $type, $scope, $AsApp, $NoAuthCheck, $skipTokenCache, $AddedHeaders, $contentType, $IgnoreErrors = $false, $returnHeaders = $false) { +function New-GraphPOSTRequest { <# .FUNCTIONALITY Internal #> + param( + $uri, + $tenantid, + $body, + $type = 'POST', + $scope, + $AsApp, + $NoAuthCheck, + $skipTokenCache, + $AddedHeaders, + $contentType, + $IgnoreErrors = $false, + $returnHeaders = $false, + $maxRetries = 1 + ) + if ($NoAuthCheck -or (Get-AuthorisedRequest -Uri $uri -TenantID $tenantid)) { $headers = Get-GraphToken -tenantid $tenantid -scope $scope -AsApp $asapp -SkipCache $skipTokenCache if ($AddedHeaders) { @@ -12,27 +28,35 @@ function New-GraphPOSTRequest ($uri, $tenantid, $body, $type, $scope, $AsApp, $N } } - if (!$type) { - $type = 'POST' - } - - Write-Information "$($type.ToUpper()) [ $uri ] | tenant: $tenantid" - if (!$contentType) { $contentType = 'application/json; charset=utf-8' } - try { - $body = Get-CIPPTextReplacement -TenantFilter $tenantid -Text $body -EscapeForJson - $ReturnedData = (Invoke-RestMethod -Uri $($uri) -Method $TYPE -Body $body -Headers $headers -ContentType $contentType -SkipHttpErrorCheck:$IgnoreErrors -ResponseHeadersVariable responseHeaders) - } catch { - $Message = if ($_.ErrorDetails.Message) { - Get-NormalizedError -Message $_.ErrorDetails.Message - } else { - $_.Exception.message + + $body = Get-CIPPTextReplacement -TenantFilter $tenantid -Text $body -EscapeForJson + + $x = 0 + do { + try { + Write-Information "$($type.ToUpper()) [ $uri ] | tenant: $tenantid | attempt: $($x + 1) of $maxRetries" + $success = $false + $ReturnedData = (Invoke-RestMethod -Uri $($uri) -Method $TYPE -Body $body -Headers $headers -ContentType $contentType -SkipHttpErrorCheck:$IgnoreErrors -ResponseHeadersVariable responseHeaders) + $success = $true + } catch { + + $Message = if ($_.ErrorDetails.Message) { + Get-NormalizedError -Message $_.ErrorDetails.Message + } else { + $_.Exception.message + } + $x++ + Start-Sleep -Seconds (2 * $x) } + } while (($x -lt $maxRetries) -and ($success -eq $false)) + if ($success -eq $false) { throw $Message } + if ($returnHeaders) { return $responseHeaders } else { From 995ca37cc1ab8cb88e407336be53e4c1303fe88e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 21 Nov 2025 22:34:34 -0500 Subject: [PATCH 04/15] Add text replacement for alert HTML and webhook content Introduces Get-CIPPTextReplacement to process $HTMLContent and $JSONContent with tenant-specific replacements before sending alerts. Ensures dynamic content personalization for both email and webhook notifications. --- Modules/CIPPCore/Public/Send-CIPPAlert.ps1 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 b/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 index cf947d6d8f82..3eee2aff1ed1 100644 --- a/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 +++ b/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 @@ -18,6 +18,11 @@ function Send-CIPPAlert { $Table = Get-CIPPTable -TableName SchedulerConfig $Filter = "RowKey eq 'CippNotifications' and PartitionKey eq 'CippNotifications'" $Config = [pscustomobject](Get-CIPPAzDataTableEntity @Table -Filter $Filter) + + if ($HTMLContent) { + $HTMLContent = Get-CIPPTextReplacement -TenantFilter $TenantFilter -Text $HTMLContent + } + if ($Type -eq 'email') { Write-Information 'Trying to send email' try { @@ -39,6 +44,7 @@ function Send-CIPPAlert { } } } + $PowerShellBody = [PSCustomObject]@{ message = @{ subject = $Title @@ -52,6 +58,7 @@ function Send-CIPPAlert { } $JSONBody = ConvertTo-Json -Compress -Depth 10 -InputObject $PowerShellBody + if ($PSCmdlet.ShouldProcess($($Recipients.EmailAddress.Address -join ', '), 'Sending email')) { $null = New-GraphPostRequest -uri 'https://graph.microsoft.com/v1.0/me/sendMail' -tenantid $env:TenantID -NoAuthCheck $true -type POST -body ($JSONBody) } @@ -93,7 +100,7 @@ function Send-CIPPAlert { if ($Type -eq 'webhook') { Write-Information 'Trying to send webhook' - + $JSONBody = Get-CIPPTextReplacement -TenantFilter $TenantFilter -Text $JSONContent -EscapeForJson try { if (![string]::IsNullOrWhiteSpace($Config.webhook) -or ![string]::IsNullOrWhiteSpace($AltWebhook)) { if ($PSCmdlet.ShouldProcess($Config.webhook, 'Sending webhook')) { From 38202c93e701dd871de1e052bda1c44e230dee3c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 22 Nov 2025 01:17:56 -0500 Subject: [PATCH 05/15] Update extension bundle and DurableTask settings Set extensionBundle version to start from 4.26.0. Added DurableTask defaultVersion, versionMatchStrategy, and versionFailureStrategy for stricter version control. --- host.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/host.json b/host.json index cb92d98a0fcc..712c1fc58769 100644 --- a/host.json +++ b/host.json @@ -5,7 +5,7 @@ }, "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[4.*, 5.0.0)" + "version": "[4.26.0, 5.0.0)" }, "functionTimeout": "00:10:00", "extensions": { @@ -15,7 +15,10 @@ "tracing": { "distributedTracingEnabled": false, "version": "None" - } + }, + "defaultVersion": "8.7.0", + "versionMatchStrategy": "Strict", + "versionFailureStrategy": "Fail" } } } From 53c35e60ead1b2256b7643794b981bea098e7254 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 22 Nov 2025 01:22:39 -0500 Subject: [PATCH 06/15] simplify cleanup --- .../CIPPCore/Public/Clear-CippDurables.ps1 | 37 ++++--------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/Modules/CIPPCore/Public/Clear-CippDurables.ps1 b/Modules/CIPPCore/Public/Clear-CippDurables.ps1 index 44c77f74e82c..6b0edd1a0caa 100644 --- a/Modules/CIPPCore/Public/Clear-CippDurables.ps1 +++ b/Modules/CIPPCore/Public/Clear-CippDurables.ps1 @@ -1,6 +1,6 @@ function Clear-CippDurables { [CmdletBinding(SupportsShouldProcess = $true)] - Param() + param() # Collect info $StorageContext = New-AzStorageContext -ConnectionString $env:AzureWebJobsStorage $FunctionName = $env:WEBSITE_SITE_NAME -replace '-', '' @@ -8,6 +8,13 @@ function Clear-CippDurables { # Get orchestrators $InstancesTable = Get-CippTable -TableName ('{0}Instances' -f $FunctionName) $HistoryTable = Get-CippTable -TableName ('{0}History' -f $FunctionName) + $QueueTable = Get-CippTable -TableName 'CippQueue' + $CippQueueTasks = Get-CippTable -TableName 'CippQueueTasks' + + Remove-AzDataTable @InstancesTable + Remove-AzDataTable @HistoryTable + Remove-AzDataTable @QueueTable + Remove-AzDataTable @CippQueueTasks $Queues = Get-AzStorageQueue -Context $StorageContext -Name ('{0}*' -f $FunctionName) | Select-Object -Property Name, ApproximateMessageCount, QueueClient @@ -19,8 +26,6 @@ function Clear-CippDurables { } } - Remove-AzDataTable @InstancesTable - Remove-AzDataTable @HistoryTable $BlobContainer = '{0}-largemessages' -f $FunctionName if (Get-AzStorageContainer -Name $BlobContainer -Context $StorageContext -ErrorAction SilentlyContinue) { Write-Information "- Removing blob container: $BlobContainer" @@ -29,32 +34,6 @@ function Clear-CippDurables { } } - $QueueTable = Get-CippTable -TableName 'CippQueue' - $CippQueue = Invoke-ListCippQueue - $QueueEntities = foreach ($Queue in $CippQueue) { - if ($Queue.Status -eq 'Running') { - $Queue.TotalTasks = $Queue.CompletedTasks - $Queue | Select-Object -Property PartitionKey, RowKey, TotalTasks - } - } - if (($QueueEntities | Measure-Object).Count -gt 0) { - if ($PSCmdlet.ShouldProcess('Queues', 'Mark Failed')) { - Update-AzDataTableEntity -Force @QueueTable -Entity $QueueEntities - } - } - - $CippQueueTasks = Get-CippTable -TableName 'CippQueueTasks' - $RunningTasks = Get-CIPPAzDataTableEntity @CippQueueTasks -Filter "PartitionKey eq 'Task' and Status eq 'Running'" -Property RowKey, PartitionKey, Status - if (($RunningTasks | Measure-Object).Count -gt 0) { - if ($PSCmdlet.ShouldProcess('Tasks', 'Mark Failed')) { - $UpdatedTasks = foreach ($Task in $RunningTasks) { - $Task.Status = 'Failed' - $Task - } - Update-AzDataTableEntity -Force @CippQueueTasks -Entity $UpdatedTasks - } - } - $null = Get-CippTable -TableName ('{0}History' -f $FunctionName) Write-Information 'Durable Orchestrators and Queues have been cleared' return $true From 24c462e887863a4c92436adbdb131f4674adfd34 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 23 Nov 2025 17:58:22 -0500 Subject: [PATCH 07/15] Enhance app template creation and logging Refactored logging to use headers instead of user principal, improved app registration handling for partner tenants, and updated response structure for better error and success reporting. Added logic to copy app registrations between tenants and create service principals as needed. --- .../Invoke-ExecCreateAppTemplate.ps1 | 112 +++++++++++++++--- 1 file changed, 98 insertions(+), 14 deletions(-) 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 6df90b2a1a65..202377450790 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 @@ -11,7 +11,7 @@ function Invoke-ExecCreateAppTemplate { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + Write-LogMessage -headers $Request.headers -API $APINAME -message 'Accessed this API' -Sev 'Debug' try { $TenantFilter = $Request.Body.TenantFilter @@ -47,7 +47,7 @@ function Invoke-ExecCreateAppTemplate { $RequiredResourceAccess = @() } } catch { - Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -message "Could not retrieve app registration for $AppId - will extract from service principal" -Sev 'Warning' + Write-LogMessage -headers $Request.headers -API $APINAME -message "Could not retrieve app registration for $AppId - will extract from service principal" -Sev 'Warning' $RequiredResourceAccess = @() } @@ -56,16 +56,91 @@ function Invoke-ExecCreateAppTemplate { $Permissions = $RequiredResourceAccess } else { # No permissions found - warn the user - Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -message "No permissions found for $AppId. The app registration may not have configured API permissions." -Sev 'Warning' + Write-LogMessage -headers $Request.headers -API $APINAME -message "No permissions found for $AppId. The app registration may not have configured API permissions." -Sev 'Warning' $Permissions = @() } } else { # For app registrations (applications) - $AppDetails = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/applications?`$filter=appId eq '$AppId'&`$select=id,appId,displayName,requiredResourceAccess" -tenantid $TenantFilter - if (-not $AppDetails -or $AppDetails.Count -eq 0) { - throw "Application not found for AppId: $AppId" + $App = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications(appId='$AppId')" -tenantid $TenantFilter + if (-not $App -or $App.Count -eq 0) { + throw "App registration not found for AppId: $AppId" } - $App = $AppDetails[0] + + $Tenant = Get-Tenants -TenantFilter $TenantFilter + if ($Tenant.customerId -ne $env:TenantID) { + $ExistingApp = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications?`$filter=displayName eq '$DisplayName'" -tenantid $env:TenantID -NoAuthCheck $true -AsApp $true + + if ($ExistingApp) { + Write-Information "App Registration $AppId already exists in partner tenant" + $AppId = $ExistingApp.appId + $App = $ExistingApp + Write-LogMessage -headers $Request.headers -API $APINAME -message "App Registration $($AppDetails.displayName) already exists in partner tenant" -Sev 'Info' + } else { + Write-Information "Copying App Registration $AppId from customer tenant $TenantFilter to partner tenant" + $PropertiesToRemove = @( + 'appId' + 'id' + 'createdDateTime' + 'deletedDateTime' + 'publisherDomain' + 'servicePrincipalLockConfiguration' + 'identifierUris' + 'applicationIdUris' + 'keyCredentials' + 'passwordCredentials' + 'isDisabled' + ) + $AppCopyBody = $App | Select-Object -Property * -ExcludeProperty $PropertiesToRemove + # Remove any null properties + $NullProperties = [System.Collections.Generic.List[string]]::new() + foreach ($Property in $AppCopyBody.PSObject.Properties.Name) { + if ($null -eq $AppCopyBody.$Property -or $AppCopyBody.$Property -eq '' -or !$AppCopyBody.$Property) { + Write-Information "Removing null property $Property from app copy body" + $NullProperties.Add($Property) + } + } + $AppCopyBody = $AppCopyBody | Select-Object -Property * -ExcludeProperty $NullProperties + if ($AppCopyBody.signInAudience -eq 'AzureADMyOrg') { + # Enterprise apps cannot be copied to another tenant + $AppCopyBody.signInAudience = 'AzureADMultipleOrgs' + } + if ($AppCopyBody.web -and $AppCopyBody.web.redirectUris) { + # Remove redirect URI settings if property exists + $AppCopyBody.web.PSObject.Properties.Remove('redirectUriSettings') + } + if ($AppCopyBody.api.oauth2PermissionScopes) { + $AppCopyBody.api.oauth2PermissionScopes = @(foreach ($Scope in $AppCopyBody.api.oauth2PermissionScopes) { + $Scope | Select-Object * -ExcludeProperty 'isPrivate' + }) + } + if ($AppCopyBody.appRoles) { + $AppCopyBody.appRoles = @(foreach ($Role in $AppCopyBody.api.appRoles) { + $Role | Select-Object * -ExcludeProperty 'isPreAuthorizationRequired', 'isPrivate' + }) + } + if ($AppCopyBody.api -and $AppCopyBody.api.tokenEncryptionSetting) { + # Remove token encryption settings if property exists + $AppCopyBody.api.PSObject.Properties.Remove('tokenEncryptionSetting') + } + + $NewApp = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/v1.0/applications' -tenantid $env:TenantID -NoAuthCheck $true -AsApp $true -type POST -body ($AppCopyBody | ConvertTo-Json -Depth 10) + + if (-not $NewApp) { + throw 'Failed to copy app registration to partner tenant' + } + + Write-Information "App Registration copied. New AppId: $($NewApp.appId)" + $App = $NewApp + $AppId = $NewApp.appId + Write-Information "Creating service principal for AppId: $AppId in partner tenant" + $Body = @{ + appId = $AppId + } + $NewSP = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/v1.0/servicePrincipals' -tenantid $env:TenantID -NoAuthCheck $true -AsApp $true -type POST -body ($Body | ConvertTo-Json -Depth 10) + Write-LogMessage -headers $Request.headers -API $APINAME -message "App Registration $($AppDetails.displayName) copied to partner tenant" -Sev 'Info' + } + } + $Permissions = if ($App.requiredResourceAccess) { $App.requiredResourceAccess } else { @() } } @@ -114,7 +189,7 @@ function Invoke-ExecCreateAppTemplate { } Add-CIPPAzDataTableEntity @PermissionsTable -Entity $PermissionEntity -Force - Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -message "Permission set created with ID: $PermissionSetId for $($Permissions.Count) resource(s)" -Sev 'Info' + Write-LogMessage -headers $Request.headers -API $APINAME -message "Permission set created with ID: $PermissionSetId for $($Permissions.Count) resource(s)" -Sev 'Info' } # Create the template @@ -156,24 +231,33 @@ function Invoke-ExecCreateAppTemplate { } $Message = "Template created: $DisplayName with $PermissionCount permission(s)" - Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -message $Message -Sev 'Info' + Write-LogMessage -headers $Request.headers -API $APINAME -message $Message -Sev 'Info' $Body = @{ - Results = $Message - TemplateId = $TemplateId + Results = @{'resultText' = $Message; 'state' = 'success' } + Metadata = @{ + TemplateId = $TemplateId + SourceTenant = $TenantFilter + } } $StatusCode = [HttpStatusCode]::OK } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create template: $ErrorMessage" -Sev 'Error' + Write-LogMessage -headers $Request.headers -API $APINAME -message "Failed to create template: $ErrorMessage" -Sev 'Error' -LogData (Get-CippException -Exception $_) + Write-Warning "Failed to create template: $ErrorMessage" + Write-Information $_.InvocationInfo.PositionMessage $Body = @{ - Results = "Failed to create template: $ErrorMessage" + Results = @(@{ + resultText = "Failed to create template: $ErrorMessage" + state = 'error' + details = Get-CippException -Exception $_ + }) } $StatusCode = [HttpStatusCode]::BadRequest } - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + return ([HttpResponseContext]@{ StatusCode = $StatusCode Body = ($Body | ConvertTo-Json -Depth 10) }) From d6e7c52f47f11585396e40eb7dfc4c9e4df08208 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 23 Nov 2025 17:58:34 -0500 Subject: [PATCH 08/15] Standardize result object keys in API client scripts Replaces 'severity' with 'state' and 'message' with 'resultText' in result objects for Invoke-ExecApiClient.ps1 and Invoke-ExecUpdateRefreshToken.ps1. Also updates error handling in Invoke-ExecUpdateRefreshToken.ps1 for consistency. --- .../CIPP/Settings/Invoke-ExecApiClient.ps1 | 2 +- .../Setup/Invoke-ExecUpdateRefreshToken.ps1 | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) 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 7e3b75de8e8f..c38ece6e144c 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 @@ -148,7 +148,7 @@ function Invoke-ExecApiClient { if (!$Client) { $Results = @{ resultText = 'API client not found' - severity = 'error' + state = 'error' } } else { $ApiConfig = New-CIPPAPIConfig -ResetSecret -AppId $Request.Body.ClientId -Headers $Request.Headers diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecUpdateRefreshToken.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecUpdateRefreshToken.ps1 index f40d2fb092d1..fcaa4156c323 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecUpdateRefreshToken.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecUpdateRefreshToken.ps1 @@ -1,4 +1,4 @@ -Function Invoke-ExecUpdateRefreshToken { +function Invoke-ExecUpdateRefreshToken { <# .FUNCTIONALITY Entrypoint,AnyTenant @@ -49,16 +49,21 @@ Function Invoke-ExecUpdateRefreshToken { $TenantName = $request.body.tenantId } $Results = @{ - 'message' = "Successfully updated the credentials for $($TenantName). You may continue to the next step, or add additional tenants if required." - 'severity' = 'success' + 'resultText' = "Successfully updated the credentials for $($TenantName). You may continue to the next step, or add additional tenants if required." + 'state' = 'success' } } catch { - $Results = [pscustomobject]@{'Results' = "Failed. $($_.InvocationInfo.ScriptLineNumber): $($_.Exception.message)"; severity = 'failed' } - } + $Results = [pscustomobject]@{ + 'Results' = @{ + resultText = "Failed. $($_.InvocationInfo.ScriptLineNumber): $($_.Exception.message)" + state = 'failed' + } + } - return ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $Results - }) + return ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Results + }) + } } From 6c3c7642b81699fc316da0c54b2143946b3493a8 Mon Sep 17 00:00:00 2001 From: Zac Richards <107489668+Zacgoose@users.noreply.github.com> Date: Mon, 24 Nov 2025 21:14:04 +0800 Subject: [PATCH 09/15] Update Invoke-AddEditTransportRule.ps1 --- .../Transport/Invoke-AddEditTransportRule.ps1 | 206 +++++++++++++++--- 1 file changed, 179 insertions(+), 27 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-AddEditTransportRule.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-AddEditTransportRule.ps1 index 519244db10bb..6d0cae8a4d61 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-AddEditTransportRule.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-AddEditTransportRule.ps1 @@ -42,8 +42,10 @@ function Invoke-AddEditTransportRule { # Extract condition fields $From = $Request.Body.From $FromScope = $Request.Body.FromScope + $FromMemberOf = $Request.Body.FromMemberOf $SentTo = $Request.Body.SentTo $SentToScope = $Request.Body.SentToScope + $SentToMemberOf = $Request.Body.SentToMemberOf $SubjectContainsWords = $Request.Body.SubjectContainsWords $SubjectMatchesPatterns = $Request.Body.SubjectMatchesPatterns $SubjectOrBodyContainsWords = $Request.Body.SubjectOrBodyContainsWords @@ -60,10 +62,21 @@ function Invoke-AddEditTransportRule { $MessageTypeMatches = $Request.Body.MessageTypeMatches $SenderDomainIs = $Request.Body.SenderDomainIs $RecipientDomainIs = $Request.Body.RecipientDomainIs + $RecipientAddressContainsWords = $Request.Body.RecipientAddressContainsWords + $RecipientAddressMatchesPatterns = $Request.Body.RecipientAddressMatchesPatterns + $AnyOfRecipientAddressContainsWords = $Request.Body.AnyOfRecipientAddressContainsWords + $AnyOfRecipientAddressMatchesPatterns = $Request.Body.AnyOfRecipientAddressMatchesPatterns + $AnyOfToHeader = $Request.Body.AnyOfToHeader + $AnyOfToHeaderMemberOf = $Request.Body.AnyOfToHeaderMemberOf + $AnyOfCcHeader = $Request.Body.AnyOfCcHeader + $AnyOfCcHeaderMemberOf = $Request.Body.AnyOfCcHeaderMemberOf + $AnyOfToCcHeader = $Request.Body.AnyOfToCcHeader + $AnyOfToCcHeaderMemberOf = $Request.Body.AnyOfToCcHeaderMemberOf $HeaderContainsWords = $Request.Body.HeaderContainsWords $HeaderContainsWordsMessageHeader = $Request.Body.HeaderContainsWordsMessageHeader $HeaderMatchesPatterns = $Request.Body.HeaderMatchesPatterns $HeaderMatchesPatternsMessageHeader = $Request.Body.HeaderMatchesPatternsMessageHeader + $SenderIpRanges = $Request.Body.SenderIpRanges # Extract action fields $DeleteMessage = $Request.Body.DeleteMessage @@ -91,8 +104,10 @@ function Invoke-AddEditTransportRule { # Extract exception fields (ExceptIf versions) $ExceptIfFrom = $Request.Body.ExceptIfFrom $ExceptIfFromScope = $Request.Body.ExceptIfFromScope + $ExceptIfFromMemberOf = $Request.Body.ExceptIfFromMemberOf $ExceptIfSentTo = $Request.Body.ExceptIfSentTo $ExceptIfSentToScope = $Request.Body.ExceptIfSentToScope + $ExceptIfSentToMemberOf = $Request.Body.ExceptIfSentToMemberOf $ExceptIfSubjectContainsWords = $Request.Body.ExceptIfSubjectContainsWords $ExceptIfSubjectMatchesPatterns = $Request.Body.ExceptIfSubjectMatchesPatterns $ExceptIfSubjectOrBodyContainsWords = $Request.Body.ExceptIfSubjectOrBodyContainsWords @@ -109,10 +124,21 @@ function Invoke-AddEditTransportRule { $ExceptIfMessageTypeMatches = $Request.Body.ExceptIfMessageTypeMatches $ExceptIfSenderDomainIs = $Request.Body.ExceptIfSenderDomainIs $ExceptIfRecipientDomainIs = $Request.Body.ExceptIfRecipientDomainIs + $ExceptIfRecipientAddressContainsWords = $Request.Body.ExceptIfRecipientAddressContainsWords + $ExceptIfRecipientAddressMatchesPatterns = $Request.Body.ExceptIfRecipientAddressMatchesPatterns + $ExceptIfAnyOfRecipientAddressContainsWords = $Request.Body.ExceptIfAnyOfRecipientAddressContainsWords + $ExceptIfAnyOfRecipientAddressMatchesPatterns = $Request.Body.ExceptIfAnyOfRecipientAddressMatchesPatterns + $ExceptIfAnyOfToHeader = $Request.Body.ExceptIfAnyOfToHeader + $ExceptIfAnyOfToHeaderMemberOf = $Request.Body.ExceptIfAnyOfToHeaderMemberOf + $ExceptIfAnyOfCcHeader = $Request.Body.ExceptIfAnyOfCcHeader + $ExceptIfAnyOfCcHeaderMemberOf = $Request.Body.ExceptIfAnyOfCcHeaderMemberOf + $ExceptIfAnyOfToCcHeader = $Request.Body.ExceptIfAnyOfToCcHeader + $ExceptIfAnyOfToCcHeaderMemberOf = $Request.Body.ExceptIfAnyOfToCcHeaderMemberOf $ExceptIfHeaderContainsWords = $Request.Body.ExceptIfHeaderContainsWords $ExceptIfHeaderContainsWordsMessageHeader = $Request.Body.ExceptIfHeaderContainsWordsMessageHeader $ExceptIfHeaderMatchesPatterns = $Request.Body.ExceptIfHeaderMatchesPatterns $ExceptIfHeaderMatchesPatternsMessageHeader = $Request.Body.ExceptIfHeaderMatchesPatternsMessageHeader + $ExceptIfSenderIpRanges = $Request.Body.ExceptIfSenderIpRanges # Helper function to process array fields function Process-ArrayField { @@ -202,13 +228,29 @@ function Invoke-AddEditTransportRule { # Process array fields for recipients/users $From = Process-ArrayField -Field $From + $FromMemberOf = Process-ArrayField -Field $FromMemberOf $SentTo = Process-ArrayField -Field $SentTo + $SentToMemberOf = Process-ArrayField -Field $SentToMemberOf + $AnyOfToHeader = Process-ArrayField -Field $AnyOfToHeader + $AnyOfToHeaderMemberOf = Process-ArrayField -Field $AnyOfToHeaderMemberOf + $AnyOfCcHeader = Process-ArrayField -Field $AnyOfCcHeader + $AnyOfCcHeaderMemberOf = Process-ArrayField -Field $AnyOfCcHeaderMemberOf + $AnyOfToCcHeader = Process-ArrayField -Field $AnyOfToCcHeader + $AnyOfToCcHeaderMemberOf = Process-ArrayField -Field $AnyOfToCcHeaderMemberOf $RedirectMessageTo = Process-ArrayField -Field $RedirectMessageTo $BlindCopyTo = Process-ArrayField -Field $BlindCopyTo $CopyTo = Process-ArrayField -Field $CopyTo $ModerateMessageByUser = Process-ArrayField -Field $ModerateMessageByUser $ExceptIfFrom = Process-ArrayField -Field $ExceptIfFrom + $ExceptIfFromMemberOf = Process-ArrayField -Field $ExceptIfFromMemberOf $ExceptIfSentTo = Process-ArrayField -Field $ExceptIfSentTo + $ExceptIfSentToMemberOf = Process-ArrayField -Field $ExceptIfSentToMemberOf + $ExceptIfAnyOfToHeader = Process-ArrayField -Field $ExceptIfAnyOfToHeader + $ExceptIfAnyOfToHeaderMemberOf = Process-ArrayField -Field $ExceptIfAnyOfToHeaderMemberOf + $ExceptIfAnyOfCcHeader = Process-ArrayField -Field $ExceptIfAnyOfCcHeader + $ExceptIfAnyOfCcHeaderMemberOf = Process-ArrayField -Field $ExceptIfAnyOfCcHeaderMemberOf + $ExceptIfAnyOfToCcHeader = Process-ArrayField -Field $ExceptIfAnyOfToCcHeader + $ExceptIfAnyOfToCcHeaderMemberOf = Process-ArrayField -Field $ExceptIfAnyOfToCcHeaderMemberOf $SenderDomainIs = Process-ArrayField -Field $SenderDomainIs $RecipientDomainIs = Process-ArrayField -Field $RecipientDomainIs $ExceptIfSenderDomainIs = Process-ArrayField -Field $ExceptIfSenderDomainIs @@ -224,6 +266,10 @@ function Invoke-AddEditTransportRule { $AttachmentContainsWords = Process-TextArrayField -Field $AttachmentContainsWords $AttachmentMatchesPatterns = Process-TextArrayField -Field $AttachmentMatchesPatterns $AttachmentExtensionMatchesWords = Process-TextArrayField -Field $AttachmentExtensionMatchesWords + $RecipientAddressContainsWords = Process-TextArrayField -Field $RecipientAddressContainsWords + $RecipientAddressMatchesPatterns = Process-TextArrayField -Field $RecipientAddressMatchesPatterns + $AnyOfRecipientAddressContainsWords = Process-TextArrayField -Field $AnyOfRecipientAddressContainsWords + $AnyOfRecipientAddressMatchesPatterns = Process-TextArrayField -Field $AnyOfRecipientAddressMatchesPatterns $HeaderContainsWords = Process-TextArrayField -Field $HeaderContainsWords $HeaderMatchesPatterns = Process-TextArrayField -Field $HeaderMatchesPatterns @@ -237,9 +283,17 @@ function Invoke-AddEditTransportRule { $ExceptIfAttachmentContainsWords = Process-TextArrayField -Field $ExceptIfAttachmentContainsWords $ExceptIfAttachmentMatchesPatterns = Process-TextArrayField -Field $ExceptIfAttachmentMatchesPatterns $ExceptIfAttachmentExtensionMatchesWords = Process-TextArrayField -Field $ExceptIfAttachmentExtensionMatchesWords + $ExceptIfRecipientAddressContainsWords = Process-TextArrayField -Field $ExceptIfRecipientAddressContainsWords + $ExceptIfRecipientAddressMatchesPatterns = Process-TextArrayField -Field $ExceptIfRecipientAddressMatchesPatterns + $ExceptIfAnyOfRecipientAddressContainsWords = Process-TextArrayField -Field $ExceptIfAnyOfRecipientAddressContainsWords + $ExceptIfAnyOfRecipientAddressMatchesPatterns = Process-TextArrayField -Field $ExceptIfAnyOfRecipientAddressMatchesPatterns $ExceptIfHeaderContainsWords = Process-TextArrayField -Field $ExceptIfHeaderContainsWords $ExceptIfHeaderMatchesPatterns = Process-TextArrayField -Field $ExceptIfHeaderMatchesPatterns + # Process IP range fields + $SenderIpRanges = Process-TextArrayField -Field $SenderIpRanges + $ExceptIfSenderIpRanges = Process-TextArrayField -Field $ExceptIfSenderIpRanges + try { # Build command parameters for transport rule $ruleParams = @{ @@ -256,22 +310,37 @@ function Invoke-AddEditTransportRule { if (($null -ne $State) -and (!$Identity)) { $ruleParams.Add('Enabled', $State) } if ($null -ne $Priority) { $ruleParams.Add('Priority', $Priority) } if ($null -ne $Comments) { $ruleParams.Add('Comments', $Comments) } - if ($null -ne $Mode -and $null -ne $Mode.value) { $ruleParams.Add('Mode', $Mode.value) } - if ($null -ne $SetAuditSeverity -and $null -ne $SetAuditSeverity.value -and $SetAuditSeverity.value -ne '') { - $ruleParams.Add('SetAuditSeverity', $SetAuditSeverity.value) + if ($null -ne $Mode) { + $modeValue = if ($Mode.value) { $Mode.value } else { $Mode } + $ruleParams.Add('Mode', $modeValue) + } + if ($null -ne $SetAuditSeverity) { + $severityValue = if ($SetAuditSeverity.value) { $SetAuditSeverity.value } else { $SetAuditSeverity } + if ($severityValue -ne '') { + $ruleParams.Add('SetAuditSeverity', $severityValue) + } } if ($null -ne $StopRuleProcessing) { $ruleParams.Add('StopRuleProcessing', $StopRuleProcessing) } - if ($null -ne $SenderAddressLocation -and $null -ne $SenderAddressLocation.value) { - $ruleParams.Add('SenderAddressLocation', $SenderAddressLocation.value) + if ($null -ne $SenderAddressLocation) { + $locationValue = if ($SenderAddressLocation.value) { $SenderAddressLocation.value } else { $SenderAddressLocation } + $ruleParams.Add('SenderAddressLocation', $locationValue) } if ($null -ne $ActivationDate -and $ActivationDate -ne '') { $ruleParams.Add('ActivationDate', $ActivationDate) } if ($null -ne $ExpiryDate -and $ExpiryDate -ne '') { $ruleParams.Add('ExpiryDate', $ExpiryDate) } # Condition parameters if ($null -ne $From -and $From.Count -gt 0) { $ruleParams.Add('From', $From) } - if ($null -ne $FromScope -and $null -ne $FromScope.value) { $ruleParams.Add('FromScope', $FromScope.value) } + if ($null -ne $FromScope) { + $fromScopeValue = if ($FromScope.value) { $FromScope.value } else { $FromScope } + $ruleParams.Add('FromScope', $fromScopeValue) + } + if ($null -ne $FromMemberOf -and $FromMemberOf.Count -gt 0) { $ruleParams.Add('FromMemberOf', $FromMemberOf) } if ($null -ne $SentTo -and $SentTo.Count -gt 0) { $ruleParams.Add('SentTo', $SentTo) } - if ($null -ne $SentToScope -and $null -ne $SentToScope.value) { $ruleParams.Add('SentToScope', $SentToScope.value) } + if ($null -ne $SentToScope) { + $sentToScopeValue = if ($SentToScope.value) { $SentToScope.value } else { $SentToScope } + $ruleParams.Add('SentToScope', $sentToScopeValue) + } + if ($null -ne $SentToMemberOf -and $SentToMemberOf.Count -gt 0) { $ruleParams.Add('SentToMemberOf', $SentToMemberOf) } if ($null -ne $SubjectContainsWords -and $SubjectContainsWords.Count -gt 0) { $ruleParams.Add('SubjectContainsWords', $SubjectContainsWords) } @@ -301,12 +370,17 @@ function Invoke-AddEditTransportRule { } if ($null -ne $AttachmentSizeOver) { $ruleParams.Add('AttachmentSizeOver', $AttachmentSizeOver) } if ($null -ne $MessageSizeOver) { $ruleParams.Add('MessageSizeOver', $MessageSizeOver) } - if ($null -ne $SCLOver -and $null -ne $SCLOver.value) { $ruleParams.Add('SCLOver', $SCLOver.value) } - if ($null -ne $WithImportance -and $null -ne $WithImportance.value) { - $ruleParams.Add('WithImportance', $WithImportance.value) + if ($null -ne $SCLOver) { + $sclValue = if ($SCLOver.value) { $SCLOver.value } else { $SCLOver } + $ruleParams.Add('SCLOver', $sclValue) } - if ($null -ne $MessageTypeMatches -and $null -ne $MessageTypeMatches.value) { - $ruleParams.Add('MessageTypeMatches', $MessageTypeMatches.value) + if ($null -ne $WithImportance) { + $importanceValue = if ($WithImportance.value) { $WithImportance.value } else { $WithImportance } + $ruleParams.Add('WithImportance', $importanceValue) + } + if ($null -ne $MessageTypeMatches) { + $messageTypeValue = if ($MessageTypeMatches.value) { $MessageTypeMatches.value } else { $MessageTypeMatches } + $ruleParams.Add('MessageTypeMatches', $messageTypeValue) } if ($null -ne $SenderDomainIs -and $SenderDomainIs.Count -gt 0) { $ruleParams.Add('SenderDomainIs', $SenderDomainIs) @@ -314,6 +388,36 @@ function Invoke-AddEditTransportRule { if ($null -ne $RecipientDomainIs -and $RecipientDomainIs.Count -gt 0) { $ruleParams.Add('RecipientDomainIs', $RecipientDomainIs) } + if ($null -ne $RecipientAddressContainsWords -and $RecipientAddressContainsWords.Count -gt 0) { + $ruleParams.Add('RecipientAddressContainsWords', $RecipientAddressContainsWords) + } + if ($null -ne $RecipientAddressMatchesPatterns -and $RecipientAddressMatchesPatterns.Count -gt 0) { + $ruleParams.Add('RecipientAddressMatchesPatterns', $RecipientAddressMatchesPatterns) + } + if ($null -ne $AnyOfRecipientAddressContainsWords -and $AnyOfRecipientAddressContainsWords.Count -gt 0) { + $ruleParams.Add('AnyOfRecipientAddressContainsWords', $AnyOfRecipientAddressContainsWords) + } + if ($null -ne $AnyOfRecipientAddressMatchesPatterns -and $AnyOfRecipientAddressMatchesPatterns.Count -gt 0) { + $ruleParams.Add('AnyOfRecipientAddressMatchesPatterns', $AnyOfRecipientAddressMatchesPatterns) + } + if ($null -ne $AnyOfToHeader -and $AnyOfToHeader.Count -gt 0) { + $ruleParams.Add('AnyOfToHeader', $AnyOfToHeader) + } + if ($null -ne $AnyOfToHeaderMemberOf -and $AnyOfToHeaderMemberOf.Count -gt 0) { + $ruleParams.Add('AnyOfToHeaderMemberOf', $AnyOfToHeaderMemberOf) + } + if ($null -ne $AnyOfCcHeader -and $AnyOfCcHeader.Count -gt 0) { + $ruleParams.Add('AnyOfCcHeader', $AnyOfCcHeader) + } + if ($null -ne $AnyOfCcHeaderMemberOf -and $AnyOfCcHeaderMemberOf.Count -gt 0) { + $ruleParams.Add('AnyOfCcHeaderMemberOf', $AnyOfCcHeaderMemberOf) + } + if ($null -ne $AnyOfToCcHeader -and $AnyOfToCcHeader.Count -gt 0) { + $ruleParams.Add('AnyOfToCcHeader', $AnyOfToCcHeader) + } + if ($null -ne $AnyOfToCcHeaderMemberOf -and $AnyOfToCcHeaderMemberOf.Count -gt 0) { + $ruleParams.Add('AnyOfToCcHeaderMemberOf', $AnyOfToCcHeaderMemberOf) + } if ($null -ne $HeaderContainsWords -and $HeaderContainsWords.Count -gt 0 -and $null -ne $HeaderContainsWordsMessageHeader) { $ruleParams.Add('HeaderContainsMessageHeader', $HeaderContainsWordsMessageHeader) $ruleParams.Add('HeaderContainsWords', $HeaderContainsWords) @@ -322,6 +426,9 @@ function Invoke-AddEditTransportRule { $ruleParams.Add('HeaderMatchesMessageHeader', $HeaderMatchesPatternsMessageHeader) $ruleParams.Add('HeaderMatchesPatterns', $HeaderMatchesPatterns) } + if ($null -ne $SenderIpRanges -and $SenderIpRanges.Count -gt 0) { + $ruleParams.Add('SenderIpRanges', $SenderIpRanges) + } # Action parameters if ($null -ne $DeleteMessage) { $ruleParams.Add('DeleteMessage', $DeleteMessage) } @@ -342,7 +449,10 @@ function Invoke-AddEditTransportRule { $ruleParams.Add('RejectMessageEnhancedStatusCode', $RejectMessageEnhancedStatusCode) } if ($null -ne $PrependSubject -and $PrependSubject -ne '') { $ruleParams.Add('PrependSubject', $PrependSubject) } - if ($null -ne $SetSCL -and $null -ne $SetSCL.value) { $ruleParams.Add('SetSCL', $SetSCL.value) } + if ($null -ne $SetSCL) { + $setSclValue = if ($SetSCL.value) { $SetSCL.value } else { $SetSCL } + $ruleParams.Add('SetSCL', $setSclValue) + } if ($null -ne $SetHeaderName -and $SetHeaderName -ne '' -and $null -ne $SetHeaderValue) { $ruleParams.Add('SetHeaderName', $SetHeaderName) $ruleParams.Add('SetHeaderValue', $SetHeaderValue) @@ -353,11 +463,13 @@ function Invoke-AddEditTransportRule { } if ($null -ne $ApplyHtmlDisclaimerText -and $ApplyHtmlDisclaimerText -ne '') { $ruleParams.Add('ApplyHtmlDisclaimerText', $ApplyHtmlDisclaimerText) - if ($null -ne $ApplyHtmlDisclaimerLocation -and $null -ne $ApplyHtmlDisclaimerLocation.value) { - $ruleParams.Add('ApplyHtmlDisclaimerLocation', $ApplyHtmlDisclaimerLocation.value) + if ($null -ne $ApplyHtmlDisclaimerLocation) { + $disclaimerLocationValue = if ($ApplyHtmlDisclaimerLocation.value) { $ApplyHtmlDisclaimerLocation.value } else { $ApplyHtmlDisclaimerLocation } + $ruleParams.Add('ApplyHtmlDisclaimerLocation', $disclaimerLocationValue) } - if ($null -ne $ApplyHtmlDisclaimerFallbackAction -and $null -ne $ApplyHtmlDisclaimerFallbackAction.value) { - $ruleParams.Add('ApplyHtmlDisclaimerFallbackAction', $ApplyHtmlDisclaimerFallbackAction.value) + if ($null -ne $ApplyHtmlDisclaimerFallbackAction) { + $disclaimerFallbackValue = if ($ApplyHtmlDisclaimerFallbackAction.value) { $ApplyHtmlDisclaimerFallbackAction.value } else { $ApplyHtmlDisclaimerFallbackAction } + $ruleParams.Add('ApplyHtmlDisclaimerFallbackAction', $disclaimerFallbackValue) } } if ($null -ne $GenerateIncidentReport -and $GenerateIncidentReport.Count -gt 0) { @@ -370,13 +482,17 @@ function Invoke-AddEditTransportRule { # Exception parameters (ExceptIf versions) if ($null -ne $ExceptIfFrom -and $ExceptIfFrom.Count -gt 0) { $ruleParams.Add('ExceptIfFrom', $ExceptIfFrom) } - if ($null -ne $ExceptIfFromScope -and $null -ne $ExceptIfFromScope.value) { - $ruleParams.Add('ExceptIfFromScope', $ExceptIfFromScope.value) + if ($null -ne $ExceptIfFromScope) { + $exceptFromScopeValue = if ($ExceptIfFromScope.value) { $ExceptIfFromScope.value } else { $ExceptIfFromScope } + $ruleParams.Add('ExceptIfFromScope', $exceptFromScopeValue) } + if ($null -ne $ExceptIfFromMemberOf -and $ExceptIfFromMemberOf.Count -gt 0) { $ruleParams.Add('ExceptIfFromMemberOf', $ExceptIfFromMemberOf) } if ($null -ne $ExceptIfSentTo -and $ExceptIfSentTo.Count -gt 0) { $ruleParams.Add('ExceptIfSentTo', $ExceptIfSentTo) } - if ($null -ne $ExceptIfSentToScope -and $null -ne $ExceptIfSentToScope.value) { - $ruleParams.Add('ExceptIfSentToScope', $ExceptIfSentToScope.value) + if ($null -ne $ExceptIfSentToScope) { + $exceptSentToScopeValue = if ($ExceptIfSentToScope.value) { $ExceptIfSentToScope.value } else { $ExceptIfSentToScope } + $ruleParams.Add('ExceptIfSentToScope', $exceptSentToScopeValue) } + if ($null -ne $ExceptIfSentToMemberOf -and $ExceptIfSentToMemberOf.Count -gt 0) { $ruleParams.Add('ExceptIfSentToMemberOf', $ExceptIfSentToMemberOf) } if ($null -ne $ExceptIfSubjectContainsWords -and $ExceptIfSubjectContainsWords.Count -gt 0) { $ruleParams.Add('ExceptIfSubjectContainsWords', $ExceptIfSubjectContainsWords) } @@ -408,14 +524,17 @@ function Invoke-AddEditTransportRule { $ruleParams.Add('ExceptIfAttachmentSizeOver', $ExceptIfAttachmentSizeOver) } if ($null -ne $ExceptIfMessageSizeOver) { $ruleParams.Add('ExceptIfMessageSizeOver', $ExceptIfMessageSizeOver) } - if ($null -ne $ExceptIfSCLOver -and $null -ne $ExceptIfSCLOver.value) { - $ruleParams.Add('ExceptIfSCLOver', $ExceptIfSCLOver.value) + if ($null -ne $ExceptIfSCLOver) { + $exceptSclValue = if ($ExceptIfSCLOver.value) { $ExceptIfSCLOver.value } else { $ExceptIfSCLOver } + $ruleParams.Add('ExceptIfSCLOver', $exceptSclValue) } - if ($null -ne $ExceptIfWithImportance -and $null -ne $ExceptIfWithImportance.value) { - $ruleParams.Add('ExceptIfWithImportance', $ExceptIfWithImportance.value) + if ($null -ne $ExceptIfWithImportance) { + $exceptImportanceValue = if ($ExceptIfWithImportance.value) { $ExceptIfWithImportance.value } else { $ExceptIfWithImportance } + $ruleParams.Add('ExceptIfWithImportance', $exceptImportanceValue) } - if ($null -ne $ExceptIfMessageTypeMatches -and $null -ne $ExceptIfMessageTypeMatches.value) { - $ruleParams.Add('ExceptIfMessageTypeMatches', $ExceptIfMessageTypeMatches.value) + if ($null -ne $ExceptIfMessageTypeMatches) { + $exceptMessageTypeValue = if ($ExceptIfMessageTypeMatches.value) { $ExceptIfMessageTypeMatches.value } else { $ExceptIfMessageTypeMatches } + $ruleParams.Add('ExceptIfMessageTypeMatches', $exceptMessageTypeValue) } if ($null -ne $ExceptIfSenderDomainIs -and $ExceptIfSenderDomainIs.Count -gt 0) { $ruleParams.Add('ExceptIfSenderDomainIs', $ExceptIfSenderDomainIs) @@ -423,6 +542,36 @@ function Invoke-AddEditTransportRule { if ($null -ne $ExceptIfRecipientDomainIs -and $ExceptIfRecipientDomainIs.Count -gt 0) { $ruleParams.Add('ExceptIfRecipientDomainIs', $ExceptIfRecipientDomainIs) } + if ($null -ne $ExceptIfRecipientAddressContainsWords -and $ExceptIfRecipientAddressContainsWords.Count -gt 0) { + $ruleParams.Add('ExceptIfRecipientAddressContainsWords', $ExceptIfRecipientAddressContainsWords) + } + if ($null -ne $ExceptIfRecipientAddressMatchesPatterns -and $ExceptIfRecipientAddressMatchesPatterns.Count -gt 0) { + $ruleParams.Add('ExceptIfRecipientAddressMatchesPatterns', $ExceptIfRecipientAddressMatchesPatterns) + } + if ($null -ne $ExceptIfAnyOfRecipientAddressContainsWords -and $ExceptIfAnyOfRecipientAddressContainsWords.Count -gt 0) { + $ruleParams.Add('ExceptIfAnyOfRecipientAddressContainsWords', $ExceptIfAnyOfRecipientAddressContainsWords) + } + if ($null -ne $ExceptIfAnyOfRecipientAddressMatchesPatterns -and $ExceptIfAnyOfRecipientAddressMatchesPatterns.Count -gt 0) { + $ruleParams.Add('ExceptIfAnyOfRecipientAddressMatchesPatterns', $ExceptIfAnyOfRecipientAddressMatchesPatterns) + } + if ($null -ne $ExceptIfAnyOfToHeader -and $ExceptIfAnyOfToHeader.Count -gt 0) { + $ruleParams.Add('ExceptIfAnyOfToHeader', $ExceptIfAnyOfToHeader) + } + if ($null -ne $ExceptIfAnyOfToHeaderMemberOf -and $ExceptIfAnyOfToHeaderMemberOf.Count -gt 0) { + $ruleParams.Add('ExceptIfAnyOfToHeaderMemberOf', $ExceptIfAnyOfToHeaderMemberOf) + } + if ($null -ne $ExceptIfAnyOfCcHeader -and $ExceptIfAnyOfCcHeader.Count -gt 0) { + $ruleParams.Add('ExceptIfAnyOfCcHeader', $ExceptIfAnyOfCcHeader) + } + if ($null -ne $ExceptIfAnyOfCcHeaderMemberOf -and $ExceptIfAnyOfCcHeaderMemberOf.Count -gt 0) { + $ruleParams.Add('ExceptIfAnyOfCcHeaderMemberOf', $ExceptIfAnyOfCcHeaderMemberOf) + } + if ($null -ne $ExceptIfAnyOfToCcHeader -and $ExceptIfAnyOfToCcHeader.Count -gt 0) { + $ruleParams.Add('ExceptIfAnyOfToCcHeader', $ExceptIfAnyOfToCcHeader) + } + if ($null -ne $ExceptIfAnyOfToCcHeaderMemberOf -and $ExceptIfAnyOfToCcHeaderMemberOf.Count -gt 0) { + $ruleParams.Add('ExceptIfAnyOfToCcHeaderMemberOf', $ExceptIfAnyOfToCcHeaderMemberOf) + } if ($null -ne $ExceptIfHeaderContainsWords -and $ExceptIfHeaderContainsWords.Count -gt 0 -and $null -ne $ExceptIfHeaderContainsWordsMessageHeader) { $ruleParams.Add('ExceptIfHeaderContainsMessageHeader', $ExceptIfHeaderContainsWordsMessageHeader) $ruleParams.Add('ExceptIfHeaderContainsWords', $ExceptIfHeaderContainsWords) @@ -431,6 +580,9 @@ function Invoke-AddEditTransportRule { $ruleParams.Add('ExceptIfHeaderMatchesMessageHeader', $ExceptIfHeaderMatchesPatternsMessageHeader) $ruleParams.Add('ExceptIfHeaderMatchesPatterns', $ExceptIfHeaderMatchesPatterns) } + if ($null -ne $ExceptIfSenderIpRanges -and $ExceptIfSenderIpRanges.Count -gt 0) { + $ruleParams.Add('ExceptIfSenderIpRanges', $ExceptIfSenderIpRanges) + } if (!$Identity) { $ExoRequestParam = @{ From bc55aede7577923afaa8096c574a4d136e35ff07 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 24 Nov 2025 08:52:35 -0500 Subject: [PATCH 10/15] fix template type --- .../HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 index e2e14b3a6ee9..5b47b16aceeb 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 @@ -20,6 +20,9 @@ function Invoke-ExecEditTemplate { $Template = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'IntuneTemplate' and RowKey eq '$GUID'" $OriginalJSON = $Template.JSON + $TemplateData = $Template.JSON | ConvertFrom-Json + $TemplateType = $TemplateData.Type + if ($Template.SHA) { $NewGuid = [guid]::NewGuid().ToString() } else { @@ -36,7 +39,7 @@ function Invoke-ExecEditTemplate { RawJson = $RawJSON DisplayName = $Request.Body.displayName Description = $Request.Body.description - templateType = $Template.Type + templateType = $TemplateType Package = $Template.Package Headers = $Request.Headers } From 38f8f406ac9c12ea8cd7682eb8767f9748199f3a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 24 Nov 2025 09:08:45 -0500 Subject: [PATCH 11/15] Add logging for tenant group evaluation Introduced a Write-LogMessage call to log the evaluation of tenants for each group, improving traceability and debugging of dynamic tenant group conditions. --- .../Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CIPPCore/Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1 b/Modules/CIPPCore/Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1 index 2ec937e023a3..8ea1a508d4c6 100644 --- a/Modules/CIPPCore/Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1 +++ b/Modules/CIPPCore/Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1 @@ -198,6 +198,7 @@ function Update-CIPPDynamicTenantGroups { $LogicOperator = if ($Group.RuleLogic -eq 'or') { ' -or ' } else { ' -and ' } $WhereString = $WhereConditions -join $LogicOperator Write-Information "Evaluating tenants with condition: $WhereString" + Write-LogMessage -API 'TenantGroups' -message "Evaluating tenants for group '$($Group.Name)' with condition: $WhereString" -sev Info $ScriptBlock = [ScriptBlock]::Create($WhereString) $MatchingTenants = $TenantObj | Where-Object $ScriptBlock From c35c63d4a02487cf259dabae2d903d4e9b5a24b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 24 Nov 2025 18:47:17 +0100 Subject: [PATCH 12/15] Fix: Add MessageCopyForSendOnBehalfEnabled to Set-CIPPMessageCopy so it behaves like the standard does --- .../Administration/Invoke-ExecCopyForSent.ps1 | 20 +++++++++++++------ .../CIPPCore/Public/Set-CIPPMessageCopy.ps1 | 19 ++++++++++++------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecCopyForSent.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecCopyForSent.ps1 index 0b31105cf10c..e676500b27bd 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecCopyForSent.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecCopyForSent.ps1 @@ -1,4 +1,4 @@ -Function Invoke-ExecCopyForSent { +function Invoke-ExecCopyForSent { <# .FUNCTIONALITY Entrypoint @@ -13,13 +13,21 @@ Function Invoke-ExecCopyForSent { # Interact with query parameters or the body of the request. - $TenantFilter = $Request.Query.TenantFilter ?? $Request.Body.TenantFilter + $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter $UserID = $Request.Query.ID ?? $Request.Body.ID - $MessageCopyForSentAsEnabled = $Request.Query.MessageCopyForSentAsEnabled ?? $Request.Body.MessageCopyForSentAsEnabled - $MessageCopyForSentAsEnabled = [System.Convert]::ToBoolean($MessageCopyForSentAsEnabled) + $MessageCopyState = $Request.Query.messageCopyState ?? $Request.Body.messageCopyState + $MessageCopyState = [System.Convert]::ToBoolean($MessageCopyState) - Try { - $Result = Set-CIPPMessageCopy -userid $UserID -tenantFilter $TenantFilter -APIName $APIName -Headers $Headers -MessageCopyForSentAsEnabled $MessageCopyForSentAsEnabled + try { + $params = @{ + UserId = $UserID + TenantFilter = $TenantFilter + APIName = $APIName + Headers = $Headers + MessageCopyForSentAsEnabled = $MessageCopyState + MessageCopyForSendOnBehalfEnabled = $MessageCopyState + } + $Result = Set-CIPPMessageCopy @params $StatusCode = [HttpStatusCode]::OK } catch { $Result = "$($_.Exception.Message)" diff --git a/Modules/CIPPCore/Public/Set-CIPPMessageCopy.ps1 b/Modules/CIPPCore/Public/Set-CIPPMessageCopy.ps1 index 512d56a75257..08e15977157e 100644 --- a/Modules/CIPPCore/Public/Set-CIPPMessageCopy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPMessageCopy.ps1 @@ -1,20 +1,27 @@ function Set-CIPPMessageCopy { [CmdletBinding()] param ( - $userid, + $UserId, [bool]$MessageCopyForSentAsEnabled, + [bool]$MessageCopyForSendOnBehalfEnabled, $TenantFilter, - $APIName = 'Manage OneDrive Access', + $APIName = 'Set message copy for sent', $Headers ) - Try { - $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-Mailbox' -cmdParams @{Identity = $userid; MessageCopyForSentAsEnabled = $MessageCopyForSentAsEnabled } - $Result = "Successfully set MessageCopyForSentAsEnabled as $MessageCopyForSentAsEnabled on $($userid)." + try { + $cmdParams = @{ + Identity = $UserId + MessageCopyForSentAsEnabled = $MessageCopyForSentAsEnabled + MessageCopyForSendOnBehalfEnabled = $MessageCopyForSendOnBehalfEnabled + + } + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-Mailbox' -cmdParams $cmdParams + $Result = "Successfully set message copy for 'Send as' as $MessageCopyForSentAsEnabled and 'Sent on behalf' as $MessageCopyForSendOnBehalfEnabled on $($UserId)." Write-LogMessage -headers $Headers -API $APIName -tenant $($TenantFilter) -message $Result -Sev 'Info' return $Result } catch { $ErrorMessage = Get-CippException -Exception $_ - $Result = "Failed to set MessageCopyForSentAsEnabled to $MessageCopyForSentAsEnabled - $($ErrorMessage.NormalizedError)" + $Result = "Failed to set message copy for 'Send as' as $MessageCopyForSentAsEnabled and 'Sent on behalf' as $MessageCopyForSendOnBehalfEnabled - $($ErrorMessage.NormalizedError)" Write-LogMessage -headers $Headers -API $APIName -tenant $($TenantFilter) -message $Result -Sev 'Error' -LogData $ErrorMessage throw $Result } From 8909eb1903d606272c94433208a2a4e963995812 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 24 Nov 2025 14:53:44 -0500 Subject: [PATCH 13/15] Add ScheduledTaskId tracking and filtering to logs Introduces script-level ScheduledTaskId variable in Push-ExecScheduledCommand and ensures its cleanup. Updates Write-LogMessage to include ScheduledTaskId in log entries. Enhances Invoke-ListLogs to support filtering by ScheduledTaskId, improving traceability of scheduled tasks in logs. Also adds cleanup for StandardInfo variable in Push-CIPPStandard. --- .../Activity Triggers/Push-ExecScheduledCommand.ps1 | 7 +++++++ .../Activity Triggers/Standards/Push-CIPPStandard.ps1 | 2 ++ Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 | 8 +++++--- Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 | 3 +++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 index ab3b168e593e..7614842279e4 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 @@ -7,6 +7,8 @@ function Push-ExecScheduledCommand { $item = $Item | ConvertTo-Json -Depth 100 | ConvertFrom-Json Write-Information "We are going to be running a scheduled task: $($Item.TaskInfo | ConvertTo-Json -Depth 10)" + $script:ScheduledTaskId = $Item.TaskInfo.RowKey + $Table = Get-CippTable -tablename 'ScheduledTasks' $task = $Item.TaskInfo $commandParameters = $Item.Parameters | ConvertTo-Json -Depth 10 | ConvertFrom-Json -AsHashtable @@ -21,10 +23,12 @@ function Push-ExecScheduledCommand { $CurrentTask = Get-AzDataTableEntity @Table -Filter "PartitionKey eq '$($task.PartitionKey)' and RowKey eq '$($task.RowKey)'" if (!$CurrentTask) { Write-Information "The task $($task.Name) for tenant $($task.Tenant) does not exist in the ScheduledTasks table. Exiting." + Remove-Variable -Name ScheduledTaskId -Scope Script -ErrorAction SilentlyContinue return } if ($CurrentTask.TaskState -eq 'Completed') { Write-Information "The task $($task.Name) for tenant $($task.Tenant) is already completed. Skipping execution." + Remove-Variable -Name ScheduledTaskId -Scope Script -ErrorAction SilentlyContinue return } @@ -69,6 +73,7 @@ function Push-ExecScheduledCommand { TaskState = 'Planned' ScheduledTime = [string]$nextRunUnixTime } + Remove-Variable -Name ScheduledTaskId -Scope Script -ErrorAction SilentlyContinue return } } @@ -94,6 +99,7 @@ function Push-ExecScheduledCommand { } Write-LogMessage -API 'Scheduler_UserTasks' -tenant $Tenant -tenantid $TenantInfo.customerId -message "Failed to execute task $($task.Name): The command $($Item.Command) does not exist." -sev Error + Remove-Variable -Name ScheduledTaskId -Scope Script -ErrorAction SilentlyContinue return } @@ -330,4 +336,5 @@ function Push-ExecScheduledCommand { if ($TaskType -ne 'Alert') { Write-LogMessage -API 'Scheduler_UserTasks' -tenant $Tenant -tenantid $TenantInfo.customerId -message "Successfully executed task: $($task.Name)" -sev Info } + Remove-Variable -Name ScheduledTaskId -Scope Script -ErrorAction SilentlyContinue } diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandard.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandard.ps1 index af92934a2aa7..b48f9f42b954 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandard.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandard.ps1 @@ -56,5 +56,7 @@ function Push-CIPPStandard { Write-Warning "Error running standard $($Standard) for tenant $($Tenant) - $($_.Exception.Message)" Write-Information $_.InvocationInfo.PositionMessage throw $_.Exception.Message + } finally { + Remove-Variable -Name StandardInfo -Scope Script -ErrorAction SilentlyContinue } } diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 index 7922fa3841e5..11bfef75b9eb 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 @@ -86,6 +86,7 @@ function Invoke-ListLogs { $TenantFilter = $Request.Query.Tenant $ApiFilter = $Request.Query.API $StandardFilter = $Request.Query.StandardTemplateId + $ScheduledTaskFilter = $Request.Query.ScheduledTaskId $StartDate = $Request.Query.StartDate ?? $Request.Query.DateFilter $EndDate = $Request.Query.EndDate ?? $Request.Query.DateFilter @@ -115,9 +116,10 @@ function Invoke-ListLogs { $Rows = Get-AzDataTableEntity @Table -Filter $Filter | Where-Object { $_.Severity -in $LogLevel -and $_.Username -like $username -and - ($TenantFilter -eq $null -or $TenantFilter -eq 'AllTenants' -or $_.Tenant -like "*$TenantFilter*" -or $_.TenantID -eq $TenantFilter) -and - ($ApiFilter -eq $null -or $_.API -match "$ApiFilter") -and - ($StandardFilter -eq $null -or $_.StandardTemplateId -eq $StandardFilter) + ([string]::IsNullOrEmpty($TenantFilter) -or $TenantFilter -eq 'AllTenants' -or $_.Tenant -like "*$TenantFilter*" -or $_.TenantID -eq $TenantFilter) -and + ([string]::IsNullOrEmpty($ApiFilter) -or $_.API -match "$ApiFilter") -and + ([string]::IsNullOrEmpty($StandardFilter) -or $_.StandardTemplateId -eq $StandardFilter) -and + ([string]::IsNullOrEmpty($ScheduledTaskFilter) -or $_.ScheduledTaskId -eq $ScheduledTaskFilter) } if ($AllowedTenants -notcontains 'AllTenants') { diff --git a/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 b/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 index 833b4d6f38eb..52127cc88471 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 @@ -76,6 +76,9 @@ function Write-LogMessage { $TableRow.ConditionalAccessTemplateId = [string]$script:StandardInfo.ConditionalAccessTemplateId } } + if ($script:ScheduledTaskId) { + $TableRow.ScheduledTaskId = [string]$script:ScheduledTaskId + } $Table.Entity = $TableRow Add-CIPPAzDataTableEntity @Table | Out-Null From ddbf50f13ab7d3bac26318339d3c2f91b11cd31c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 24 Nov 2025 14:57:35 -0500 Subject: [PATCH 14/15] bump version Bumped the default version and latest version reference from 8.7.0 to 8.7.1 in host.json and version_latest.txt to reflect the new release. --- host.json | 2 +- version_latest.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/host.json b/host.json index 712c1fc58769..2cf91f7b1f33 100644 --- a/host.json +++ b/host.json @@ -16,7 +16,7 @@ "distributedTracingEnabled": false, "version": "None" }, - "defaultVersion": "8.7.0", + "defaultVersion": "8.7.1", "versionMatchStrategy": "Strict", "versionFailureStrategy": "Fail" } diff --git a/version_latest.txt b/version_latest.txt index df5119ec64e6..d139a75408e9 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -8.7.0 +8.7.1 From bc3c5d648e8ddf21a5234f74cfb4c74b59687dcf Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 24 Nov 2025 15:39:49 -0500 Subject: [PATCH 15/15] Add StandardTaskFilter check to log listing Updated Invoke-ListLogs.ps1 to require $StandardTaskFilter to be set before processing rows with StandardTemplateId. This change ensures standard template filtering only occurs when explicitly requested. --- Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 index 11bfef75b9eb..92682df3bf50 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 @@ -128,7 +128,7 @@ function Invoke-ListLogs { foreach ($Row in $Rows) { if ($AllowedTenants -contains 'AllTenants' -or ($AllowedTenants -notcontains 'AllTenants' -and ($TenantList.defaultDomainName -contains $Row.Tenant -or $Row.Tenant -eq 'CIPP' -or $TenantList.customerId -contains $Row.TenantId)) ) { - if ($Row.StandardTemplateId) { + if ($StandardTaskFilter -and $Row.StandardTemplateId) { $Standard = ($Templates | Where-Object { $_.RowKey -eq $Row.StandardTemplateId }).JSON | ConvertFrom-Json $StandardInfo = @{