Skip to content

Commit 24ba12e

Browse files
Merge branch 'dev' of https://github.com/KelvinTegelaar/CIPP-API into dev
2 parents e476fa3 + 2e320e3 commit 24ba12e

File tree

3 files changed

+147
-35
lines changed

3 files changed

+147
-35
lines changed

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecBackendURLs.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ function Invoke-ExecBackendURLs {
3232
RGName = $RGName
3333
FunctionName = $env:WEBSITE_SITE_NAME
3434
SWAName = $SWAName
35+
Hosted = $env:CIPP_HOSTED -eq 'true' ?? $false
36+
OS = $IsLinux ? 'Linux' : 'Windows'
37+
SKU = $env:WEBSITE_SKU
38+
Timezone = $env:WEBSITE_TIME_ZONE ?? 'UTC'
39+
BusinessHoursStart = $env:CIPP_BUSINESS_HOURS_START ?? '09:00'
40+
BusinessHoursEnd = $env:CIPP_BUSINESS_HOURS_END ?? '17:00'
3541
}
3642

3743

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
function Invoke-ExecTimeSettings {
2+
<#
3+
.FUNCTIONALITY
4+
Entrypoint
5+
.ROLE
6+
CIPP.SuperAdmin.ReadWrite
7+
#>
8+
[CmdletBinding()]
9+
param($Request, $TriggerMetadata)
10+
11+
try {
12+
$Subscription = $env:WEBSITE_OWNER_NAME -split '\+' | Select-Object -First 1
13+
$Owner = $env:WEBSITE_OWNER_NAME
14+
15+
if ($env:WEBSITE_SKU -ne 'FlexConsumption' -and $Owner -match '^(?<SubscriptionId>[^+]+)\+(?<RGName>[^-]+(?:-[^-]+)*?)(?:-[^-]+webspace(?:-Linux)?)?$') {
16+
$RGName = $Matches.RGName
17+
} else {
18+
$RGName = $env:WEBSITE_RESOURCE_GROUP
19+
}
20+
21+
$FunctionName = $env:WEBSITE_SITE_NAME
22+
$Timezone = $Request.Body.Timezone.value ?? $Request.Body.Timezone
23+
$BusinessHoursStart = $Request.Body.BusinessHoursStart.value ?? $Request.Body.BusinessHoursStart
24+
25+
# Validate timezone format
26+
if (-not $Timezone) {
27+
throw 'Timezone is required'
28+
}
29+
30+
if (!$IsLinux) {
31+
# Get Timezone standard name for Windows
32+
$Timezone = Get-TimeZone -Id $Timezone | Select-Object -ExpandProperty StandardName
33+
}
34+
35+
# Calculate business hours end time (10 hours after start)
36+
$BusinessHoursEnd = $null
37+
if ($env:WEBSITE_SKU -eq 'FlexConsumption') {
38+
if (-not $BusinessHoursStart) {
39+
throw 'Business hours start time is required for Flex Consumption plans'
40+
}
41+
42+
# Validate time format (HH:mm)
43+
if ($BusinessHoursStart -notmatch '^\d{2}:\d{2}$') {
44+
throw 'Business hours start time must be in HH:mm format'
45+
}
46+
47+
# Calculate end time (start + 10 hours)
48+
$StartTime = [DateTime]::ParseExact($BusinessHoursStart, 'HH:mm', $null)
49+
$EndTime = $StartTime.AddHours(10)
50+
$BusinessHoursEnd = $EndTime.ToString('HH:mm')
51+
}
52+
53+
Write-Information "Updating function app time settings: Timezone=$Timezone, BusinessHoursStart=$BusinessHoursStart, BusinessHoursEnd=$BusinessHoursEnd"
54+
55+
# Build app settings hashtable
56+
$AppSettings = @{
57+
'WEBSITE_TIME_ZONE' = $Timezone
58+
}
59+
60+
if ($env:WEBSITE_SKU -eq 'FlexConsumption') {
61+
$AppSettings['CIPP_BUSINESS_HOURS_START'] = $BusinessHoursStart
62+
$AppSettings['CIPP_BUSINESS_HOURS_END'] = $BusinessHoursEnd
63+
}
64+
65+
# Update app settings using native cmdlet (managed identity authentication handled automatically)
66+
Update-AzFunctionAppSetting -Name $FunctionName -ResourceGroupName $RGName -AppSetting $AppSettings -ErrorAction Stop | Out-Null
67+
68+
Write-LogMessage -API 'ExecTimeSettings' -headers $Request.Headers -message "Updated time settings: Timezone=$Timezone, BusinessHours=$BusinessHoursStart-$BusinessHoursEnd" -Sev 'Info'
69+
70+
$Results = @{
71+
Results = 'Time settings updated successfully. Please note that timezone changes may require a function app restart to take effect.'
72+
Timezone = $Timezone
73+
SKU = $env:WEBSITE_SKU
74+
}
75+
76+
if ($env:WEBSITE_SKU -eq 'FlexConsumption') {
77+
$Results.BusinessHoursStart = $BusinessHoursStart
78+
$Results.BusinessHoursEnd = $BusinessHoursEnd
79+
}
80+
81+
return ([HttpResponseContext]@{
82+
StatusCode = [httpstatusCode]::OK
83+
Body = $Results
84+
})
85+
86+
} catch {
87+
$ErrorMessage = Get-CippException -Exception $_
88+
Write-LogMessage -API 'ExecTimeSettings' -headers $Request.Headers -message "Failed to update time settings: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage
89+
90+
return ([HttpResponseContext]@{
91+
StatusCode = [httpstatusCode]::BadRequest
92+
Body = @{
93+
Results = "Failed to update time settings: $($ErrorMessage.NormalizedError)"
94+
}
95+
})
96+
}
97+
}

Modules/CIPPCore/Public/Get-CIPPTimerFunctions.ps1

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,8 @@ function Get-CIPPTimerFunctions {
4040
}
4141

4242
$CIPPRoot = (Get-Item $CIPPCoreModuleRoot).Parent.Parent
43-
Write-Host "Loading CIPPTimers from $CIPPRoot\CIPPTimers.json"
4443
$CippTimers = Get-Content -Path $CIPPRoot\CIPPTimers.json
45-
Write-Host "CIPPTimers content: $CippTimers"
44+
4645
if ($ListAllTasks) {
4746
$Orchestrators = $CippTimers | ConvertFrom-Json | Sort-Object -Property Priority
4847
} else {
@@ -61,45 +60,47 @@ function Get-CIPPTimerFunctions {
6160
}
6261

6362
foreach ($Orchestrator in $Orchestrators) {
64-
$Status = $OrchestratorStatus | Where-Object { $_.RowKey -eq $Orchestrator.Id }
65-
if ($Status.Cron) {
66-
$CronString = $Status.Cron
67-
} else {
68-
$CronString = $Orchestrator.Cron
69-
}
70-
$CronCount = ($CronString -split ' ' | Measure-Object).Count
71-
if ($CronCount -eq 5) {
72-
$Cron = [Ncrontab.Advanced.CrontabSchedule]::Parse($CronString)
73-
} elseif ($CronCount -eq 6) {
74-
$Cron = [Ncrontab.Advanced.CrontabSchedule]::Parse($CronString, [Ncrontab.Advanced.Enumerations.CronStringFormat]::WithSeconds)
75-
} else {
76-
Write-Warning "Invalid cron expression for $($Orchestrator.Command): $($Orchestrator.Cron)"
77-
continue
78-
}
63+
if (Get-Command -Name $Orchestrator.Command -Module CIPPCore -ErrorAction SilentlyContinue) {
64+
$Status = $OrchestratorStatus | Where-Object { $_.RowKey -eq $Orchestrator.Id }
65+
if ($Status.Cron -and $Orchestrator.IsSystem -eq $true -and -not $ResetToDefault.IsPresent) {
66+
$CronString = $Status.Cron
67+
} else {
68+
$CronString = $Orchestrator.Cron
69+
}
7970

80-
if (!$ListAllTasks.IsPresent) {
81-
if ($Orchestrator.PreferredProcessor -and $AvailableNodes -contains $Orchestrator.PreferredProcessor -and $Node -ne $Orchestrator.PreferredProcessor) {
82-
# only run on preferred processor when available
83-
continue
84-
} elseif ((!$Orchestrator.PreferredProcessor -or $AvailableNodes -notcontains $Orchestrator.PreferredProcessor) -and $Node -notin ('http', 'proc')) {
85-
# Catchall function nodes
71+
$CronCount = ($CronString -split ' ' | Measure-Object).Count
72+
if ($CronCount -eq 5) {
73+
$Cron = [Ncrontab.Advanced.CrontabSchedule]::Parse($CronString)
74+
} elseif ($CronCount -eq 6) {
75+
$Cron = [Ncrontab.Advanced.CrontabSchedule]::Parse($CronString, [Ncrontab.Advanced.Enumerations.CronStringFormat]::WithSeconds)
76+
} else {
77+
Write-Warning "Invalid cron expression for $($Orchestrator.Command): $($Orchestrator.Cron)"
8678
continue
8779
}
88-
}
8980

90-
$Now = Get-Date
91-
if ($ListAllTasks.IsPresent) {
92-
$NextOccurrence = [datetime]$Cron.GetNextOccurrence($Now)
93-
} else {
94-
$NextOccurrences = $Cron.GetNextOccurrences($Now.AddMinutes(-15), $Now.AddMinutes(15))
95-
if (!$Status -or $Status.LastOccurrence -eq 'Never') {
96-
$NextOccurrence = $NextOccurrences | Where-Object { $_ -le (Get-Date) } | Select-Object -First 1
81+
if (!$ListAllTasks.IsPresent) {
82+
if ($Orchestrator.PreferredProcessor -and $AvailableNodes -contains $Orchestrator.PreferredProcessor -and $Node -ne $Orchestrator.PreferredProcessor) {
83+
# only run on preferred processor when available
84+
continue
85+
} elseif ((!$Orchestrator.PreferredProcessor -or $AvailableNodes -notcontains $Orchestrator.PreferredProcessor) -and $Node -notin ('http', 'proc')) {
86+
# Catchall function nodes
87+
continue
88+
}
89+
}
90+
91+
$Now = Get-Date
92+
if ($ListAllTasks.IsPresent) {
93+
$NextOccurrence = [datetime]$Cron.GetNextOccurrence($Now)
9794
} else {
98-
$NextOccurrence = $NextOccurrences | Where-Object { $_ -gt $Status.LastOccurrence.DateTime.ToLocalTime() -and $_ -le (Get-Date) } | Select-Object -First 1
95+
$NextOccurrences = $Cron.GetNextOccurrences($Now.AddMinutes(-15), $Now.AddMinutes(15))
96+
if (!$Status -or $Status.LastOccurrence -eq 'Never') {
97+
$NextOccurrence = $NextOccurrences | Where-Object { $_ -le (Get-Date) } | Select-Object -First 1
98+
} else {
99+
$NextOccurrence = $NextOccurrences | Where-Object { $_ -gt $Status.LastOccurrence.DateTime.ToLocalTime() -and $_ -le (Get-Date) } | Select-Object -First 1
100+
}
99101
}
100-
}
101102

102-
if (Get-Command -Name $Orchestrator.Command -Module CIPPCore -ErrorAction SilentlyContinue) {
103+
103104
if ($NextOccurrence -or $ListAllTasks.IsPresent) {
104105
if (!$Status) {
105106
$Status = [pscustomobject]@{
@@ -117,8 +118,9 @@ function Get-CIPPTimerFunctions {
117118
}
118119
Add-CIPPAzDataTableEntity @Table -Entity $Status -Force
119120
} else {
121+
$Status.Command = $Orchestrator.Command
120122
if ($Orchestrator.IsSystem -eq $true -or $ResetToDefault.IsPresent) {
121-
$Status.Cron = $CronString
123+
$Status.Cron = $Orchestrator.Cron
122124
}
123125
$Status.NextOccurrence = $NextOccurrence.ToUniversalTime()
124126
$PreferredProcessor = $Orchestrator.PreferredProcessor ?? ''
@@ -153,4 +155,11 @@ function Get-CIPPTimerFunctions {
153155
}
154156
}
155157
}
158+
159+
foreach ($StaleStatus in $OrchestratorStatus) {
160+
if ($Orchestrators.Id -notcontains $StaleStatus.RowKey) {
161+
Write-Warning "Removing stale timer function entry: $($StaleStatus.RowKey)"
162+
Remove-AzDataTableEntity @Table -Entity $StaleStatus
163+
}
164+
}
156165
}

0 commit comments

Comments
 (0)