@@ -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