Skip to content

Commit 1aee380

Browse files
authored
Merge pull request #55 from KelvinTegelaar/master
[pull] master from KelvinTegelaar:master
2 parents fe78dba + 952c237 commit 1aee380

16 files changed

+326
-193
lines changed

Config/SchedulerRateLimits.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

Modules/CIPPCore/Public/Add-CIPPDbItem.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ function Add-CIPPDbItem {
4141

4242
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
4343
[Alias('Data')]
44+
[AllowNull()]
4445
[AllowEmptyCollection()]
4546
$InputObject,
4647

Modules/CIPPCore/Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
$TenantFilter
1212
)
1313

14+
#Add rerun protection: This Monitor can only run once every hour.
15+
$Rerun = Test-CIPPRerun -TenantFilter $TenantFilter -Type 'ExchangeMonitor' -API 'Get-CIPPAlertQuarantineReleaseRequests'
16+
if ($Rerun) {
17+
return $true
18+
}
1419
$HasLicense = Test-CIPPStandardLicense -StandardName 'QuarantineReleaseRequests' -TenantFilter $TenantFilter -RequiredCapabilities @(
1520
'EXCHANGE_S_STANDARD',
1621
'EXCHANGE_S_ENTERPRISE',
@@ -20,11 +25,11 @@
2025
)
2126

2227
if (-not $HasLicense) {
23-
return
28+
return $true
2429
}
2530

2631
try {
27-
$RequestedReleases = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-QuarantineMessage' -cmdParams @{ PageSize = 1000; ReleaseStatus = 'Requested' } -ErrorAction Stop | Select-Object -ExcludeProperty *data.type*
32+
$RequestedReleases = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-QuarantineMessage' -cmdParams @{ PageSize = 1000; ReleaseStatus = 'Requested'; StartReceivedDate = (Get-Date).AddHours(-6) } -ErrorAction Stop | Select-Object -ExcludeProperty *data.type*
2833

2934
if ($RequestedReleases) {
3035
# Get the CIPP URL for the Quarantine link

Modules/CIPPCore/Public/Assert-CippVersion.ps1

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ function Assert-CippVersion {
1010
Local version of CIPP frontend
1111
1212
#>
13-
Param($CIPPVersion)
14-
$APIVersion = (Get-Content 'version_latest.txt' -Raw).trim()
13+
param($CIPPVersion)
14+
$CIPPCoreModuleRoot = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase
15+
$CIPPRoot = (Get-Item $CIPPCoreModuleRoot).Parent.Parent
16+
$APIVersion = (Get-Content -Path $CIPPRoot\version_latest.txt).trim()
1517

1618
$RemoteAPIVersion = (Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/KelvinTegelaar/CIPP-API/master/version_latest.txt').trim()
1719
$RemoteCIPPVersion = (Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/KelvinTegelaar/CIPP/main/public/version.json').version

Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenantDownload.ps1

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,19 @@ function Push-AuditLogTenantDownload {
3939
$LogSearchesTable = Get-CippTable -TableName 'AuditLogSearches'
4040

4141
try {
42-
$LogSearches = Get-CippAuditLogSearches -TenantFilter $TenantFilter -ReadyToProcess | Select-Object -First 10
43-
Write-Information ('Audit Logs: Found {0} searches, begin downloading' -f $LogSearches.Count)
42+
$LogSearches = Get-CippAuditLogSearches -TenantFilter $TenantFilter -ReadyToProcess | Sort-Object -Property filterStartDateTime | Select-Object -First 10
43+
if ($LogSearches.Count -eq 0) {
44+
Write-Information "Audit Logs: No searches ready to process for $TenantFilter"
45+
return $true
46+
}
47+
Write-Information ('Audit Logs: Found {0} searches for {1}, begin downloading' -f $LogSearches.Count, $TenantFilter)
4448
foreach ($Search in $LogSearches) {
4549
$SearchEntity = Get-CIPPAzDataTableEntity @LogSearchesTable -Filter "Tenant eq '$($TenantFilter)' and RowKey eq '$($Search.id)'"
4650
$SearchEntity.CippStatus = 'Processing'
4751
Add-CIPPAzDataTableEntity @LogSearchesTable -Entity $SearchEntity -Force
4852
try {
4953
Write-Information "Audit Log search: Processing search ID: $($Search.id) for tenant: $TenantFilter"
50-
$Downloads = New-CIPPAuditLogSearchResultsCache -TenantFilter $TenantFilter -searchId $Search.id
54+
$null = New-CIPPAuditLogSearchResultsCache -TenantFilter $TenantFilter -searchId $Search.id
5155
$SearchEntity.CippStatus = 'Downloaded'
5256
} catch {
5357
if ($_.Exception.Message -match 'Request rate is large. More Request Units may be needed, so no changes were made. Please retry this request later.') {
@@ -68,6 +72,7 @@ function Push-AuditLogTenantDownload {
6872
}
6973
Add-CIPPAzDataTableEntity @LogSearchesTable -Entity $SearchEntity -Force
7074
}
75+
return $true
7176
} catch {
7277
Write-Information ('Audit Log search: Error {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message)
7378
return $false

Modules/CIPPCore/Public/Entrypoints/Invoke-ListExtensionsConfig.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ function Invoke-ListExtensionsConfig {
1010
$Table = Get-CIPPTable -TableName Extensionsconfig
1111
try {
1212
$Config = (Get-CIPPAzDataTableEntity @Table).config
13-
if (Test-Json -Json $Config -ErrorAction SilentlyContinue) {
13+
if ($Config -and (Test-Json -Json $Config -ErrorAction SilentlyContinue)) {
1414
$Body = $Config | ConvertFrom-Json -Depth 10 -ErrorAction Stop
1515
if ($Body.HaloPSA.TicketType -and !$Body.HaloPSA.TicketType.value) {
1616
# translate ticket type to autocomplete format

Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreation.ps1

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ function Start-AuditLogSearchCreation {
4545
}
4646

4747
if (!$TenantInConfig) {
48-
Write-Information "Tenant $($Tenant.defaultDomainName) has no configured audit log rules, skipping search creation."
4948
continue
5049
}
5150

@@ -67,7 +66,7 @@ function Start-AuditLogSearchCreation {
6766
SkipLog = $true
6867
}
6968
Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress)
70-
Write-Information "Started Audit Log search creation orchestratorwith $($Batch.Count) tenants"
69+
Write-Information "Started Audit Log search creation orchestrator with $($Batch.Count) tenants"
7170
} else {
7271
Write-Information 'No tenants found for Audit Log search creation'
7372
}

Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-UserTasksOrchestrator.ps1

Lines changed: 30 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,6 @@ function Start-UserTasksOrchestrator {
1717
$Filter = "PartitionKey eq 'ScheduledTask' and (TaskState eq 'Planned' or TaskState eq 'Failed - Planned' or (TaskState eq 'Pending' and Timestamp lt datetime'$30MinutesAgo') or (TaskState eq 'Running' and Timestamp lt datetime'$4HoursAgo'))"
1818
$tasks = Get-CIPPAzDataTableEntity @Table -Filter $Filter
1919

20-
$RateLimitTable = Get-CIPPTable -tablename 'SchedulerRateLimits'
21-
$RateLimits = Get-CIPPAzDataTableEntity @RateLimitTable -Filter "PartitionKey eq 'SchedulerRateLimits'"
22-
23-
$CIPPCoreModuleRoot = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase
24-
$CIPPRoot = (Get-Item $CIPPCoreModuleRoot).Parent.Parent
25-
$DefaultRateLimits = Get-Content -Path "$CIPPRoot/Config/SchedulerRateLimits.json" | ConvertFrom-Json
26-
$NewRateLimits = foreach ($Limit in $DefaultRateLimits) {
27-
if ($Limit.Command -notin $RateLimits.RowKey) {
28-
@{
29-
PartitionKey = 'SchedulerRateLimits'
30-
RowKey = $Limit.Command
31-
MaxRequests = $Limit.MaxRequests
32-
}
33-
}
34-
}
35-
36-
if ($NewRateLimits) {
37-
$null = Add-CIPPAzDataTableEntity @RateLimitTable -Entity $NewRateLimits -Force
38-
$RateLimits = Get-CIPPAzDataTableEntity @RateLimitTable -Filter "PartitionKey eq 'SchedulerRateLimits'"
39-
}
40-
41-
# Create a hashtable for quick rate limit lookups
42-
$RateLimitLookup = @{}
43-
foreach ($limit in $RateLimits) {
44-
$RateLimitLookup[$limit.RowKey] = $limit.MaxRequests
45-
}
46-
4720
$Batch = [System.Collections.Generic.List[object]]::new()
4821
$TenantList = Get-Tenants -IncludeErrors
4922
foreach ($task in $tasks) {
@@ -62,9 +35,12 @@ function Start-UserTasksOrchestrator {
6235
TaskState = 'Pending'
6336
}
6437
$task.Parameters = $task.Parameters | ConvertFrom-Json -AsHashtable
65-
$task.AdditionalProperties = $task.AdditionalProperties | ConvertFrom-Json
66-
6738
if (!$task.Parameters) { $task.Parameters = @{} }
39+
40+
# Cache Get-Command result to avoid repeated expensive reflection calls
41+
$CommandInfo = Get-Command $task.Command
42+
$HasTenantFilter = $CommandInfo.Parameters.ContainsKey('TenantFilter')
43+
6844
$ScheduledCommand = [pscustomobject]@{
6945
Command = $task.Command
7046
Parameters = $task.Parameters
@@ -77,13 +53,15 @@ function Start-UserTasksOrchestrator {
7753
Write-Host "Excluded Tenants from this task: $ExcludedTenants"
7854
$AllTenantCommands = foreach ($Tenant in $TenantList | Where-Object { $_.defaultDomainName -notin $ExcludedTenants }) {
7955
$NewParams = $task.Parameters.Clone()
80-
if ((Get-Command $task.Command).Parameters.TenantFilter) {
56+
if ($HasTenantFilter) {
8157
$NewParams.TenantFilter = $Tenant.defaultDomainName
8258
}
59+
# Clone TaskInfo to prevent shared object references
60+
$TaskInfoClone = $task.PSObject.Copy()
8361
[pscustomobject]@{
8462
Command = $task.Command
8563
Parameters = $NewParams
86-
TaskInfo = $task
64+
TaskInfo = $TaskInfoClone
8765
FunctionName = 'ExecScheduledCommand'
8866
}
8967
}
@@ -109,13 +87,15 @@ function Start-UserTasksOrchestrator {
10987

11088
$GroupTenantCommands = foreach ($ExpandedTenant in $ExpandedTenants | Where-Object { $_.value -notin $ExcludedTenants }) {
11189
$NewParams = $task.Parameters.Clone()
112-
if ((Get-Command $task.Command).Parameters.TenantFilter) {
90+
if ($HasTenantFilter) {
11391
$NewParams.TenantFilter = $ExpandedTenant.value
11492
}
93+
# Clone TaskInfo to prevent shared object references
94+
$TaskInfoClone = $task.PSObject.Copy()
11595
[pscustomobject]@{
11696
Command = $task.Command
11797
Parameters = $NewParams
118-
TaskInfo = $task
98+
TaskInfo = $TaskInfoClone
11999
FunctionName = 'ExecScheduledCommand'
120100
}
121101
}
@@ -125,14 +105,14 @@ function Start-UserTasksOrchestrator {
125105
Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Failed to expand tenant group for task $($task.Name): $($_.Exception.Message)" -sev Error
126106

127107
# Fall back to treating as single tenant
128-
if ((Get-Command $task.Command).Parameters.TenantFilter) {
108+
if ($HasTenantFilter) {
129109
$ScheduledCommand.Parameters['TenantFilter'] = $task.Tenant
130110
}
131111
$Batch.Add($ScheduledCommand)
132112
}
133113
} else {
134114
# Handle single tenant
135-
if ((Get-Command $task.Command).Parameters.TenantFilter) {
115+
if ($HasTenantFilter) {
136116
$ScheduledCommand.Parameters['TenantFilter'] = $task.Tenant
137117
}
138118
$Batch.Add($ScheduledCommand)
@@ -155,51 +135,35 @@ function Start-UserTasksOrchestrator {
155135
Write-Information 'Batching tasks for execution...'
156136
Write-Information "Total tasks to process: $($Batch.Count)"
157137

158-
if (($Batch | Measure-Object).Count -gt 0) {
159-
# Group commands by type and apply rate limits
160-
$CommandGroups = $Batch | Group-Object -Property Command
138+
if ($Batch.Count -gt 0) {
139+
# Group tasks by tenant instead of command type
140+
$TenantGroups = $Batch | Group-Object -Property { $_.Parameters.TenantFilter }
161141
$ProcessedBatches = [System.Collections.Generic.List[object]]::new()
162142

163-
foreach ($CommandGroup in $CommandGroups) {
164-
$CommandName = $CommandGroup.Name
165-
$Commands = [System.Collections.Generic.List[object]]::new($CommandGroup.Group)
166-
167-
# Get rate limit for this command (default to 100 if not found)
168-
$MaxItemsPerBatch = if ($RateLimitLookup.ContainsKey($CommandName)) {
169-
$RateLimitLookup[$CommandName]
170-
} else {
171-
100
172-
}
173-
174-
# Split into batches based on rate limit
175-
while ($Commands.Count -gt 0) {
176-
$BatchSize = [Math]::Min($Commands.Count, $MaxItemsPerBatch)
177-
$CommandBatch = [System.Collections.Generic.List[object]]::new()
178-
179-
for ($i = 0; $i -lt $BatchSize; $i++) {
180-
$CommandBatch.Add($Commands[0])
181-
$Commands.RemoveAt(0)
182-
}
143+
foreach ($TenantGroup in $TenantGroups) {
144+
$TenantName = $TenantGroup.Name
145+
$TenantCommands = [System.Collections.Generic.List[object]]::new($TenantGroup.Group)
183146

184-
$ProcessedBatches.Add($CommandBatch)
185-
}
147+
Write-Information "Creating batch for tenant: $TenantName with $($TenantCommands.Count) tasks"
148+
$ProcessedBatches.Add($TenantCommands)
186149
}
187150

188-
# Process each batch separately
151+
# Process each tenant batch separately
189152
foreach ($ProcessedBatch in $ProcessedBatches) {
190-
Write-Information "Processing batch with $($ProcessedBatch.Count) tasks..."
153+
$TenantName = $ProcessedBatch[0].Parameters.TenantFilter
154+
Write-Information "Processing batch for tenant: $TenantName with $($ProcessedBatch.Count) tasks..."
191155
Write-Information 'Tasks by command:'
192156
$ProcessedBatch | Group-Object -Property Command | ForEach-Object {
193157
Write-Information " - $($_.Name): $($_.Count)"
194158
}
195159

196-
# Create queue entry for each batch
197-
$Queue = New-CippQueueEntry -Name "Scheduled Tasks - Batch #$($ProcessedBatches.IndexOf($ProcessedBatch) + 1) of $($ProcessedBatches.Count)"
160+
# Create queue entry for each tenant batch
161+
$Queue = New-CippQueueEntry -Name "Scheduled Tasks - $TenantName"
198162
$QueueId = $Queue.RowKey
199-
$BatchWithQueue = $ProcessedBatch | Select-Object *, @{Name = 'QueueId'; Expression = { $QueueId } }, @{Name = 'QueueName'; Expression = { '{0} - {1}' -f $_.TaskInfo.Name, ($_.TaskInfo.Tenant -ne 'AllTenants' ? $_.TaskInfo.Tenant : $_.Parameters.TenantFilter) } }
163+
$BatchWithQueue = $ProcessedBatch | Select-Object *, @{Name = 'QueueId'; Expression = { $QueueId } }, @{Name = 'QueueName'; Expression = { '{0} - {1}' -f $_.TaskInfo.Name, $TenantName } }
200164

201165
$InputObject = [PSCustomObject]@{
202-
OrchestratorName = 'UserTaskOrchestrator'
166+
OrchestratorName = "UserTaskOrchestrator_$TenantName"
203167
Batch = @($BatchWithQueue)
204168
SkipLog = $true
205169
}

0 commit comments

Comments
 (0)