@@ -10,10 +10,39 @@ function Start-UserTasksOrchestrator {
1010 $1HourAgo = (Get-Date ).AddHours(-1 ).ToUniversalTime().ToString(' yyyy-MM-ddTHH:mm:ssZ' )
1111 $Filter = " PartitionKey eq 'ScheduledTask' and (TaskState eq 'Planned' or TaskState eq 'Failed - Planned' or (TaskState eq 'Running' and Timestamp lt datetime'$1HourAgo '))"
1212 $tasks = Get-CIPPAzDataTableEntity @Table - Filter $Filter
13+
14+ $RateLimitTable = Get-CIPPTable - tablename ' SchedulerRateLimits'
15+ $RateLimits = Get-CIPPAzDataTableEntity @RateLimitTable - Filter " PartitionKey eq 'SchedulerRateLimits'"
16+
17+ $CIPPCoreModuleRoot = Get-Module - Name CIPPCore | Select-Object - ExpandProperty ModuleBase
18+ $CIPPRoot = (Get-Item $CIPPCoreModuleRoot ).Parent.Parent
19+ $DefaultRateLimits = Get-Content - Path " $CIPPRoot /Config/SchedulerRateLimits.json" | ConvertFrom-Json
20+ $NewRateLimits = foreach ($Limit in $DefaultRateLimits ) {
21+ if ($Limit.Command -notin $RateLimits.RowKey ) {
22+ @ {
23+ PartitionKey = ' SchedulerRateLimits'
24+ RowKey = $Limit.Command
25+ MaxRequests = $Limit.MaxRequests
26+ }
27+ }
28+ }
29+
30+ if ($NewRateLimits ) {
31+ $null = Add-CIPPAzDataTableEntity @RateLimitTable - Entity $NewRateLimits - Force
32+ $RateLimits = Get-CIPPAzDataTableEntity @RateLimitTable - Filter " PartitionKey eq 'SchedulerRateLimits'"
33+ }
34+
35+ # Create a hashtable for quick rate limit lookups
36+ $RateLimitLookup = @ {}
37+ foreach ($limit in $RateLimits ) {
38+ $RateLimitLookup [$limit.RowKey ] = $limit.MaxRequests
39+ }
40+
1341 $Batch = [System.Collections.Generic.List [object ]]::new()
1442 $TenantList = Get-Tenants - IncludeErrors
1543 foreach ($task in $tasks ) {
1644 $tenant = $task.Tenant
45+
1746 $currentUnixTime = [int64 ](([datetime ]::UtcNow) - (Get-Date ' 1/1/1970' )).TotalSeconds
1847 if ($currentUnixTime -ge $task.ScheduledTime ) {
1948 try {
@@ -113,21 +142,62 @@ function Start-UserTasksOrchestrator {
113142 }
114143 }
115144 }
145+
146+ Write-Information ' Batching tasks for execution...'
147+ Write-Information " Total tasks to process: $ ( $Batch.Count ) "
148+
116149 if (($Batch | Measure-Object ).Count -gt 0 ) {
117- # Create queue entry
118- $Queue = New-CippQueueEntry - Name ' Scheduled Tasks' - TotalTasks ($Batch | Measure-Object ).Count
119- $QueueId = $Queue.RowKey
120- $Batch = $Batch | Select-Object * , @ {Name = ' QueueId' ; Expression = { $QueueId } }, @ {Name = ' QueueName' ; Expression = { ' {0} - {1}' -f $_.TaskInfo.Name , ($_.TaskInfo.Tenant -ne ' AllTenants' ? $_.TaskInfo.Tenant : $_.Parameters.TenantFilter ) } }
121-
122- $InputObject = [PSCustomObject ]@ {
123- OrchestratorName = ' UserTaskOrchestrator'
124- Batch = @ ($Batch )
125- SkipLog = $true
150+ # Group commands by type and apply rate limits
151+ $CommandGroups = $Batch | Group-Object - Property Command
152+ $ProcessedBatches = [System.Collections.Generic.List [object ]]::new()
153+
154+ foreach ($CommandGroup in $CommandGroups ) {
155+ $CommandName = $CommandGroup.Name
156+ $Commands = [System.Collections.Generic.List [object ]]::new($CommandGroup.Group )
157+
158+ # Get rate limit for this command (default to 100 if not found)
159+ $MaxItemsPerBatch = if ($RateLimitLookup.ContainsKey ($CommandName )) {
160+ $RateLimitLookup [$CommandName ]
161+ } else {
162+ 100
163+ }
164+
165+ # Split into batches based on rate limit
166+ while ($Commands.Count -gt 0 ) {
167+ $BatchSize = [Math ]::Min($Commands.Count , $MaxItemsPerBatch )
168+ $CommandBatch = [System.Collections.Generic.List [object ]]::new()
169+
170+ for ($i = 0 ; $i -lt $BatchSize ; $i ++ ) {
171+ $CommandBatch.Add ($Commands [0 ])
172+ $Commands.RemoveAt (0 )
173+ }
174+
175+ $ProcessedBatches.Add ($CommandBatch )
176+ }
126177 }
127- # Write-Host ($InputObject | ConvertTo-Json -Depth 10)
128178
129- if ($PSCmdlet.ShouldProcess (' Start-UserTasksOrchestrator' , ' Starting User Tasks Orchestrator' )) {
130- Start-NewOrchestration - FunctionName ' CIPPOrchestrator' - InputObject ($InputObject | ConvertTo-Json - Depth 10 - Compress)
179+ # Process each batch separately
180+ foreach ($ProcessedBatch in $ProcessedBatches ) {
181+ Write-Information " Processing batch with $ ( $ProcessedBatch.Count ) tasks..."
182+ Write-Information ' Tasks by command:'
183+ $ProcessedBatch | Group-Object - Property Command | ForEach-Object {
184+ Write-Information " - $ ( $_.Name ) : $ ( $_.Count ) "
185+ }
186+
187+ # Create queue entry for each batch
188+ $Queue = New-CippQueueEntry - Name " Scheduled Tasks - Batch #$ ( $ProcessedBatches.IndexOf ($ProcessedBatch ) + 1 ) of $ ( $ProcessedBatches.Count ) "
189+ $QueueId = $Queue.RowKey
190+ $BatchWithQueue = $ProcessedBatch | Select-Object * , @ {Name = ' QueueId' ; Expression = { $QueueId } }, @ {Name = ' QueueName' ; Expression = { ' {0} - {1}' -f $_.TaskInfo.Name , ($_.TaskInfo.Tenant -ne ' AllTenants' ? $_.TaskInfo.Tenant : $_.Parameters.TenantFilter ) } }
191+
192+ $InputObject = [PSCustomObject ]@ {
193+ OrchestratorName = ' UserTaskOrchestrator'
194+ Batch = @ ($BatchWithQueue )
195+ SkipLog = $true
196+ }
197+
198+ if ($PSCmdlet.ShouldProcess (' Start-UserTasksOrchestrator' , ' Starting User Tasks Orchestrator' )) {
199+ Start-NewOrchestration - FunctionName ' CIPPOrchestrator' - InputObject ($InputObject | ConvertTo-Json - Depth 10 - Compress)
200+ }
131201 }
132202 }
133203}
0 commit comments