Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions Config/SchedulerRateLimits.json

This file was deleted.

1 change: 1 addition & 0 deletions Modules/CIPPCore/Public/Add-CIPPDbItem.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ function Add-CIPPDbItem {

[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[Alias('Data')]
[AllowNull()]
[AllowEmptyCollection()]
$InputObject,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
$TenantFilter
)

#Add rerun protection: This Monitor can only run once every hour.
$Rerun = Test-CIPPRerun -TenantFilter $TenantFilter -Type 'ExchangeMonitor' -API 'Get-CIPPAlertQuarantineReleaseRequests'
if ($Rerun) {
return $true
}
$HasLicense = Test-CIPPStandardLicense -StandardName 'QuarantineReleaseRequests' -TenantFilter $TenantFilter -RequiredCapabilities @(
'EXCHANGE_S_STANDARD',
'EXCHANGE_S_ENTERPRISE',
Expand All @@ -20,11 +25,11 @@
)

if (-not $HasLicense) {
return
return $true
}

try {
$RequestedReleases = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-QuarantineMessage' -cmdParams @{ PageSize = 1000; ReleaseStatus = 'Requested' } -ErrorAction Stop | Select-Object -ExcludeProperty *data.type*
$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*

if ($RequestedReleases) {
# Get the CIPP URL for the Quarantine link
Expand Down
6 changes: 4 additions & 2 deletions Modules/CIPPCore/Public/Assert-CippVersion.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ function Assert-CippVersion {
Local version of CIPP frontend

#>
Param($CIPPVersion)
$APIVersion = (Get-Content 'version_latest.txt' -Raw).trim()
param($CIPPVersion)
$CIPPCoreModuleRoot = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase
$CIPPRoot = (Get-Item $CIPPCoreModuleRoot).Parent.Parent
$APIVersion = (Get-Content -Path $CIPPRoot\version_latest.txt).trim()

$RemoteAPIVersion = (Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/KelvinTegelaar/CIPP-API/master/version_latest.txt').trim()
$RemoteCIPPVersion = (Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/KelvinTegelaar/CIPP/main/public/version.json').version
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,19 @@ function Push-AuditLogTenantDownload {
$LogSearchesTable = Get-CippTable -TableName 'AuditLogSearches'

try {
$LogSearches = Get-CippAuditLogSearches -TenantFilter $TenantFilter -ReadyToProcess | Select-Object -First 10
Write-Information ('Audit Logs: Found {0} searches, begin downloading' -f $LogSearches.Count)
$LogSearches = Get-CippAuditLogSearches -TenantFilter $TenantFilter -ReadyToProcess | Sort-Object -Property filterStartDateTime | Select-Object -First 10
if ($LogSearches.Count -eq 0) {
Write-Information "Audit Logs: No searches ready to process for $TenantFilter"
return $true
}
Write-Information ('Audit Logs: Found {0} searches for {1}, begin downloading' -f $LogSearches.Count, $TenantFilter)
foreach ($Search in $LogSearches) {
$SearchEntity = Get-CIPPAzDataTableEntity @LogSearchesTable -Filter "Tenant eq '$($TenantFilter)' and RowKey eq '$($Search.id)'"
$SearchEntity.CippStatus = 'Processing'
Add-CIPPAzDataTableEntity @LogSearchesTable -Entity $SearchEntity -Force
try {
Write-Information "Audit Log search: Processing search ID: $($Search.id) for tenant: $TenantFilter"
$Downloads = New-CIPPAuditLogSearchResultsCache -TenantFilter $TenantFilter -searchId $Search.id
$null = New-CIPPAuditLogSearchResultsCache -TenantFilter $TenantFilter -searchId $Search.id
$SearchEntity.CippStatus = 'Downloaded'
} catch {
if ($_.Exception.Message -match 'Request rate is large. More Request Units may be needed, so no changes were made. Please retry this request later.') {
Expand All @@ -68,6 +72,7 @@ function Push-AuditLogTenantDownload {
}
Add-CIPPAzDataTableEntity @LogSearchesTable -Entity $SearchEntity -Force
}
return $true
} catch {
Write-Information ('Audit Log search: Error {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message)
return $false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function Invoke-ListExtensionsConfig {
$Table = Get-CIPPTable -TableName Extensionsconfig
try {
$Config = (Get-CIPPAzDataTableEntity @Table).config
if (Test-Json -Json $Config -ErrorAction SilentlyContinue) {
if ($Config -and (Test-Json -Json $Config -ErrorAction SilentlyContinue)) {
$Body = $Config | ConvertFrom-Json -Depth 10 -ErrorAction Stop
if ($Body.HaloPSA.TicketType -and !$Body.HaloPSA.TicketType.value) {
# translate ticket type to autocomplete format
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ function Start-AuditLogSearchCreation {
}

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

Expand All @@ -67,7 +66,7 @@ function Start-AuditLogSearchCreation {
SkipLog = $true
}
Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress)
Write-Information "Started Audit Log search creation orchestratorwith $($Batch.Count) tenants"
Write-Information "Started Audit Log search creation orchestrator with $($Batch.Count) tenants"
} else {
Write-Information 'No tenants found for Audit Log search creation'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,6 @@ function Start-UserTasksOrchestrator {
$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'))"
$tasks = Get-CIPPAzDataTableEntity @Table -Filter $Filter

$RateLimitTable = Get-CIPPTable -tablename 'SchedulerRateLimits'
$RateLimits = Get-CIPPAzDataTableEntity @RateLimitTable -Filter "PartitionKey eq 'SchedulerRateLimits'"

$CIPPCoreModuleRoot = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase
$CIPPRoot = (Get-Item $CIPPCoreModuleRoot).Parent.Parent
$DefaultRateLimits = Get-Content -Path "$CIPPRoot/Config/SchedulerRateLimits.json" | ConvertFrom-Json
$NewRateLimits = foreach ($Limit in $DefaultRateLimits) {
if ($Limit.Command -notin $RateLimits.RowKey) {
@{
PartitionKey = 'SchedulerRateLimits'
RowKey = $Limit.Command
MaxRequests = $Limit.MaxRequests
}
}
}

if ($NewRateLimits) {
$null = Add-CIPPAzDataTableEntity @RateLimitTable -Entity $NewRateLimits -Force
$RateLimits = Get-CIPPAzDataTableEntity @RateLimitTable -Filter "PartitionKey eq 'SchedulerRateLimits'"
}

# Create a hashtable for quick rate limit lookups
$RateLimitLookup = @{}
foreach ($limit in $RateLimits) {
$RateLimitLookup[$limit.RowKey] = $limit.MaxRequests
}

$Batch = [System.Collections.Generic.List[object]]::new()
$TenantList = Get-Tenants -IncludeErrors
foreach ($task in $tasks) {
Expand All @@ -62,9 +35,12 @@ function Start-UserTasksOrchestrator {
TaskState = 'Pending'
}
$task.Parameters = $task.Parameters | ConvertFrom-Json -AsHashtable
$task.AdditionalProperties = $task.AdditionalProperties | ConvertFrom-Json

if (!$task.Parameters) { $task.Parameters = @{} }

# Cache Get-Command result to avoid repeated expensive reflection calls
$CommandInfo = Get-Command $task.Command
$HasTenantFilter = $CommandInfo.Parameters.ContainsKey('TenantFilter')

$ScheduledCommand = [pscustomobject]@{
Command = $task.Command
Parameters = $task.Parameters
Expand All @@ -77,13 +53,15 @@ function Start-UserTasksOrchestrator {
Write-Host "Excluded Tenants from this task: $ExcludedTenants"
$AllTenantCommands = foreach ($Tenant in $TenantList | Where-Object { $_.defaultDomainName -notin $ExcludedTenants }) {
$NewParams = $task.Parameters.Clone()
if ((Get-Command $task.Command).Parameters.TenantFilter) {
if ($HasTenantFilter) {
$NewParams.TenantFilter = $Tenant.defaultDomainName
}
# Clone TaskInfo to prevent shared object references
$TaskInfoClone = $task.PSObject.Copy()
[pscustomobject]@{
Command = $task.Command
Parameters = $NewParams
TaskInfo = $task
TaskInfo = $TaskInfoClone
FunctionName = 'ExecScheduledCommand'
}
}
Expand All @@ -109,13 +87,15 @@ function Start-UserTasksOrchestrator {

$GroupTenantCommands = foreach ($ExpandedTenant in $ExpandedTenants | Where-Object { $_.value -notin $ExcludedTenants }) {
$NewParams = $task.Parameters.Clone()
if ((Get-Command $task.Command).Parameters.TenantFilter) {
if ($HasTenantFilter) {
$NewParams.TenantFilter = $ExpandedTenant.value
}
# Clone TaskInfo to prevent shared object references
$TaskInfoClone = $task.PSObject.Copy()
[pscustomobject]@{
Command = $task.Command
Parameters = $NewParams
TaskInfo = $task
TaskInfo = $TaskInfoClone
FunctionName = 'ExecScheduledCommand'
}
}
Expand All @@ -125,14 +105,14 @@ function Start-UserTasksOrchestrator {
Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Failed to expand tenant group for task $($task.Name): $($_.Exception.Message)" -sev Error

# Fall back to treating as single tenant
if ((Get-Command $task.Command).Parameters.TenantFilter) {
if ($HasTenantFilter) {
$ScheduledCommand.Parameters['TenantFilter'] = $task.Tenant
}
$Batch.Add($ScheduledCommand)
}
} else {
# Handle single tenant
if ((Get-Command $task.Command).Parameters.TenantFilter) {
if ($HasTenantFilter) {
$ScheduledCommand.Parameters['TenantFilter'] = $task.Tenant
}
$Batch.Add($ScheduledCommand)
Expand All @@ -155,51 +135,35 @@ function Start-UserTasksOrchestrator {
Write-Information 'Batching tasks for execution...'
Write-Information "Total tasks to process: $($Batch.Count)"

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

foreach ($CommandGroup in $CommandGroups) {
$CommandName = $CommandGroup.Name
$Commands = [System.Collections.Generic.List[object]]::new($CommandGroup.Group)

# Get rate limit for this command (default to 100 if not found)
$MaxItemsPerBatch = if ($RateLimitLookup.ContainsKey($CommandName)) {
$RateLimitLookup[$CommandName]
} else {
100
}

# Split into batches based on rate limit
while ($Commands.Count -gt 0) {
$BatchSize = [Math]::Min($Commands.Count, $MaxItemsPerBatch)
$CommandBatch = [System.Collections.Generic.List[object]]::new()

for ($i = 0; $i -lt $BatchSize; $i++) {
$CommandBatch.Add($Commands[0])
$Commands.RemoveAt(0)
}
foreach ($TenantGroup in $TenantGroups) {
$TenantName = $TenantGroup.Name
$TenantCommands = [System.Collections.Generic.List[object]]::new($TenantGroup.Group)

$ProcessedBatches.Add($CommandBatch)
}
Write-Information "Creating batch for tenant: $TenantName with $($TenantCommands.Count) tasks"
$ProcessedBatches.Add($TenantCommands)
}

# Process each batch separately
# Process each tenant batch separately
foreach ($ProcessedBatch in $ProcessedBatches) {
Write-Information "Processing batch with $($ProcessedBatch.Count) tasks..."
$TenantName = $ProcessedBatch[0].Parameters.TenantFilter
Write-Information "Processing batch for tenant: $TenantName with $($ProcessedBatch.Count) tasks..."
Write-Information 'Tasks by command:'
$ProcessedBatch | Group-Object -Property Command | ForEach-Object {
Write-Information " - $($_.Name): $($_.Count)"
}

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

$InputObject = [PSCustomObject]@{
OrchestratorName = 'UserTaskOrchestrator'
OrchestratorName = "UserTaskOrchestrator_$TenantName"
Batch = @($BatchWithQueue)
SkipLog = $true
}
Expand Down
Loading