Skip to content

Commit 0b2be27

Browse files
authored
Merge pull request #353 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 15babfd + 1837ec2 commit 0b2be27

File tree

9 files changed

+343
-193
lines changed

9 files changed

+343
-193
lines changed

Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1

Lines changed: 152 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -24,149 +24,178 @@ function Add-CIPPScheduledTask {
2424
$Headers
2525
)
2626

27-
$Table = Get-CIPPTable -TableName 'ScheduledTasks'
28-
29-
if ($RunNow.IsPresent -and $RowKey) {
30-
try {
31-
$Filter = "PartitionKey eq 'ScheduledTask' and RowKey eq '$($RowKey)'"
32-
$ExistingTask = (Get-CIPPAzDataTableEntity @Table -Filter $Filter)
33-
$ExistingTask.ScheduledTime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds
34-
$ExistingTask.TaskState = 'Planned'
35-
Add-CIPPAzDataTableEntity @Table -Entity $ExistingTask -Force
36-
Write-LogMessage -headers $Headers -API 'RunNow' -message "Task $($ExistingTask.Name) scheduled to run now" -Sev 'Info' -Tenant $ExistingTask.Tenant
37-
return "Task $($ExistingTask.Name) scheduled to run now"
38-
} catch {
39-
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
40-
Write-LogMessage -headers $Headers -API 'RunNow' -message "Could not run task: $ErrorMessage" -Sev 'Error'
41-
return "Could not run task: $ErrorMessage"
42-
}
43-
} else {
44-
if ($DisallowDuplicateName) {
45-
$Filter = "PartitionKey eq 'ScheduledTask' and Name eq '$($Task.Name)'"
46-
$ExistingTask = (Get-CIPPAzDataTableEntity @Table -Filter $Filter)
47-
if ($ExistingTask) {
48-
return "Task with name $($Task.Name) already exists"
49-
}
50-
}
27+
try {
5128

52-
$propertiesToCheck = @('Webhook', 'Email', 'PSA')
53-
$PostExecutionObject = ($propertiesToCheck | Where-Object { $task.PostExecution.$_ -eq $true })
54-
$PostExecution = $PostExecutionObject ? ($PostExecutionObject -join ',') : ($Task.PostExecution.value -join ',')
55-
$Parameters = [System.Collections.Hashtable]@{}
56-
foreach ($Key in $task.Parameters.PSObject.Properties.Name) {
57-
$Param = $task.Parameters.$Key
29+
$Table = Get-CIPPTable -TableName 'ScheduledTasks'
5830

59-
if ($null -eq $Param -or $Param -eq '' -or ($Param | Measure-Object).Count -eq 0) {
60-
continue
31+
if ($RunNow.IsPresent -and $RowKey) {
32+
try {
33+
$Filter = "PartitionKey eq 'ScheduledTask' and RowKey eq '$($RowKey)'"
34+
$ExistingTask = (Get-CIPPAzDataTableEntity @Table -Filter $Filter)
35+
$ExistingTask.ScheduledTime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds
36+
$ExistingTask.TaskState = 'Planned'
37+
Add-CIPPAzDataTableEntity @Table -Entity $ExistingTask -Force
38+
Write-LogMessage -headers $Headers -API 'RunNow' -message "Task $($ExistingTask.Name) scheduled to run now" -Sev 'Info' -Tenant $ExistingTask.Tenant
39+
return "Task $($ExistingTask.Name) scheduled to run now"
40+
} catch {
41+
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
42+
Write-LogMessage -headers $Headers -API 'RunNow' -message "Could not run task: $ErrorMessage" -Sev 'Error'
43+
return "Could not run task: $ErrorMessage"
6144
}
62-
if ($Param -is [System.Collections.IDictionary] -and $Param.Key) {
63-
$ht = @{}
64-
foreach ($p in $Param.GetEnumerator()) {
65-
$ht[$p.Key] = $p.Value
45+
} else {
46+
if ($DisallowDuplicateName) {
47+
$Filter = "PartitionKey eq 'ScheduledTask' and Name eq '$($Task.Name)'"
48+
$ExistingTask = (Get-CIPPAzDataTableEntity @Table -Filter $Filter)
49+
if ($ExistingTask) {
50+
return "Task with name $($Task.Name) already exists"
6651
}
67-
$Parameters[$Key] = [PSCustomObject]$ht
68-
} else {
69-
$Parameters[$Key] = $Param
7052
}
71-
}
7253

73-
if ($Headers) {
74-
$Parameters.Headers = $Headers | Select-Object -Property 'x-forwarded-for', 'x-ms-client-principal', 'x-ms-client-principal-idp', 'x-ms-client-principal-name'
75-
}
54+
$propertiesToCheck = @('Webhook', 'Email', 'PSA')
55+
$PostExecutionObject = ($propertiesToCheck | Where-Object { $task.PostExecution.$_ -eq $true })
56+
$PostExecution = $PostExecutionObject ? ($PostExecutionObject -join ',') : ($Task.PostExecution.value -join ',')
57+
$Parameters = [System.Collections.Hashtable]@{}
58+
foreach ($Key in $task.Parameters.PSObject.Properties.Name) {
59+
$Param = $task.Parameters.$Key
60+
61+
if ($null -eq $Param -or $Param -eq '' -or ($Param | Measure-Object).Count -eq 0) {
62+
continue
63+
}
7664

77-
$Parameters = ($Parameters | ConvertTo-Json -Depth 10 -Compress)
78-
$AdditionalProperties = [System.Collections.Hashtable]@{}
79-
foreach ($Prop in $task.AdditionalProperties) {
80-
if ($null -eq $Prop.Value -or $Prop.Value -eq '' -or ($Prop.Value | Measure-Object).Count -eq 0) {
81-
continue
65+
# handle different object types in params
66+
if ($Param -is [System.Collections.IDictionary] -or $Param[0].Key) {
67+
Write-Information "Parameter $Key is a hashtable"
68+
$ht = @{}
69+
foreach ($p in $Param.GetEnumerator()) {
70+
$ht[$p.Key] = $p.Value
71+
}
72+
$Parameters[$Key] = [PSCustomObject]$ht
73+
Write-Information "Converted $Key to PSObject $($Parameters[$Key] | ConvertTo-Json -Compress)"
74+
} elseif ($Param -is [System.Object[]] -and -not ($Param -is [string])) {
75+
Write-Information "Parameter $Key is an enumerable object"
76+
$Param = $Param | ForEach-Object {
77+
if ($null -eq $_) {
78+
# Skip null entries
79+
return
80+
}
81+
if ($_ -is [System.Collections.IDictionary]) {
82+
[PSCustomObject]$_
83+
} elseif ($_ -is [PSCustomObject]) {
84+
$_
85+
} else {
86+
$_
87+
}
88+
} | Where-Object { $null -ne $_ }
89+
$Parameters[$Key] = $Param
90+
} else {
91+
Write-Information "Parameter $Key is a simple value"
92+
$Parameters[$Key] = $Param
93+
}
8294
}
83-
$AdditionalProperties[$Prop.Key] = $Prop.Value
84-
}
85-
$AdditionalProperties = ([PSCustomObject]$AdditionalProperties | ConvertTo-Json -Compress)
86-
if ($Parameters -eq 'null') { $Parameters = '' }
87-
if (!$Task.RowKey) {
88-
$RowKey = (New-Guid).Guid
89-
} else {
90-
$RowKey = $Task.RowKey
91-
}
9295

93-
$Recurrence = if ([string]::IsNullOrEmpty($task.Recurrence.value)) {
94-
$task.Recurrence
95-
} else {
96-
$task.Recurrence.value
97-
}
96+
if ($Headers) {
97+
$Parameters.Headers = $Headers | Select-Object -Property 'x-forwarded-for', 'x-ms-client-principal', 'x-ms-client-principal-idp', 'x-ms-client-principal-name'
98+
}
9899

99-
if ([int64]$task.ScheduledTime -eq 0 -or [string]::IsNullOrEmpty($task.ScheduledTime)) {
100-
$task.ScheduledTime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds
101-
}
102-
$excludedTenants = if ($task.excludedTenants.value) {
103-
$task.excludedTenants.value -join ','
104-
}
100+
$Parameters = ($Parameters | ConvertTo-Json -Depth 10 -Compress)
101+
$AdditionalProperties = [System.Collections.Hashtable]@{}
102+
foreach ($Prop in $task.AdditionalProperties) {
103+
if ($null -eq $Prop.Value -or $Prop.Value -eq '' -or ($Prop.Value | Measure-Object).Count -eq 0) {
104+
continue
105+
}
106+
$AdditionalProperties[$Prop.Key] = $Prop.Value
107+
}
108+
$AdditionalProperties = ([PSCustomObject]$AdditionalProperties | ConvertTo-Json -Compress)
109+
if ($Parameters -eq 'null') { $Parameters = '' }
110+
if (!$Task.RowKey) {
111+
$RowKey = (New-Guid).Guid
112+
} else {
113+
$RowKey = $Task.RowKey
114+
}
105115

106-
# Handle tenant filter - support both single tenant and tenant groups
107-
$tenantFilter = $task.TenantFilter.value ? $task.TenantFilter.value : $task.TenantFilter
108-
$originalTenantFilter = $task.TenantFilter
116+
$Recurrence = if ([string]::IsNullOrEmpty($task.Recurrence.value)) {
117+
$task.Recurrence
118+
} else {
119+
$task.Recurrence.value
120+
}
109121

110-
# If tenant filter is a complex object (from form), extract the value
111-
if ($tenantFilter -is [PSCustomObject] -and $tenantFilter.value) {
112-
$originalTenantFilter = $tenantFilter
113-
$tenantFilter = $tenantFilter.value
114-
}
122+
if ([int64]$task.ScheduledTime -eq 0 -or [string]::IsNullOrEmpty($task.ScheduledTime)) {
123+
$task.ScheduledTime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds
124+
}
125+
$excludedTenants = if ($task.excludedTenants.value) {
126+
$task.excludedTenants.value -join ','
127+
}
115128

116-
# If tenant filter is a string but still seems to be JSON, try to parse it
117-
if ($tenantFilter -is [string] -and $tenantFilter.StartsWith('{')) {
118-
try {
119-
$parsedTenantFilter = $tenantFilter | ConvertFrom-Json
120-
if ($parsedTenantFilter.value) {
121-
$originalTenantFilter = $parsedTenantFilter
122-
$tenantFilter = $parsedTenantFilter.value
129+
# Handle tenant filter - support both single tenant and tenant groups
130+
$tenantFilter = $task.TenantFilter.value ? $task.TenantFilter.value : $task.TenantFilter
131+
$originalTenantFilter = $task.TenantFilter
132+
133+
# If tenant filter is a complex object (from form), extract the value
134+
if ($tenantFilter -is [PSCustomObject] -and $tenantFilter.value) {
135+
$originalTenantFilter = $tenantFilter
136+
$tenantFilter = $tenantFilter.value
137+
}
138+
139+
# If tenant filter is a string but still seems to be JSON, try to parse it
140+
if ($tenantFilter -is [string] -and $tenantFilter.StartsWith('{')) {
141+
try {
142+
$parsedTenantFilter = $tenantFilter | ConvertFrom-Json
143+
if ($parsedTenantFilter.value) {
144+
$originalTenantFilter = $parsedTenantFilter
145+
$tenantFilter = $parsedTenantFilter.value
146+
}
147+
} catch {
148+
# If parsing fails, use the string as is
149+
Write-Warning "Could not parse tenant filter JSON: $tenantFilter"
123150
}
124-
} catch {
125-
# If parsing fails, use the string as is
126-
Write-Warning "Could not parse tenant filter JSON: $tenantFilter"
127151
}
128-
}
129152

130-
$entity = @{
131-
PartitionKey = [string]'ScheduledTask'
132-
TaskState = [string]'Planned'
133-
RowKey = [string]$RowKey
134-
Tenant = [string]$tenantFilter
135-
excludedTenants = [string]$excludedTenants
136-
Name = [string]$task.Name
137-
Command = [string]$task.Command.value
138-
Parameters = [string]$Parameters
139-
ScheduledTime = [string]$task.ScheduledTime
140-
Recurrence = [string]$Recurrence
141-
PostExecution = [string]$PostExecution
142-
AdditionalProperties = [string]$AdditionalProperties
143-
Hidden = [bool]$Hidden
144-
Results = 'Planned'
145-
}
153+
$entity = @{
154+
PartitionKey = [string]'ScheduledTask'
155+
TaskState = [string]'Planned'
156+
RowKey = [string]$RowKey
157+
Tenant = [string]$tenantFilter
158+
excludedTenants = [string]$excludedTenants
159+
Name = [string]$task.Name
160+
Command = [string]$task.Command.value
161+
Parameters = [string]$Parameters
162+
ScheduledTime = [string]$task.ScheduledTime
163+
Recurrence = [string]$Recurrence
164+
PostExecution = [string]$PostExecution
165+
AdditionalProperties = [string]$AdditionalProperties
166+
Hidden = [bool]$Hidden
167+
Results = 'Planned'
168+
}
146169

147-
# Store the original tenant filter for group expansion during execution
148-
if ($originalTenantFilter -is [PSCustomObject] -and $originalTenantFilter.type -eq 'Group') {
149-
$entity['TenantGroup'] = [string]($originalTenantFilter | ConvertTo-Json -Compress)
150-
} elseif ($originalTenantFilter -is [string] -and $originalTenantFilter.StartsWith('{')) {
151-
# Check if it's a serialized group object
152-
try {
153-
$parsedOriginal = $originalTenantFilter | ConvertFrom-Json
154-
if ($parsedOriginal.type -eq 'Group') {
155-
$entity['TenantGroup'] = [string]$originalTenantFilter
170+
# Store the original tenant filter for group expansion during execution
171+
if ($originalTenantFilter -is [PSCustomObject] -and $originalTenantFilter.type -eq 'Group') {
172+
$entity['TenantGroup'] = [string]($originalTenantFilter | ConvertTo-Json -Compress)
173+
} elseif ($originalTenantFilter -is [string] -and $originalTenantFilter.StartsWith('{')) {
174+
# Check if it's a serialized group object
175+
try {
176+
$parsedOriginal = $originalTenantFilter | ConvertFrom-Json
177+
if ($parsedOriginal.type -eq 'Group') {
178+
$entity['TenantGroup'] = [string]$originalTenantFilter
179+
}
180+
} catch {
181+
# Not a JSON object, ignore
156182
}
183+
}
184+
if ($SyncType) {
185+
$entity.SyncType = $SyncType
186+
}
187+
try {
188+
Add-CIPPAzDataTableEntity @Table -Entity $entity -Force
157189
} catch {
158-
# Not a JSON object, ignore
190+
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
191+
return "Could not add task: $ErrorMessage"
159192
}
193+
return "Successfully added task: $($entity.Name)"
160194
}
161-
if ($SyncType) {
162-
$entity.SyncType = $SyncType
163-
}
164-
try {
165-
Add-CIPPAzDataTableEntity @Table -Entity $entity -Force
166-
} catch {
167-
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
168-
return "Could not add task: $ErrorMessage"
169-
}
170-
return "Successfully added task: $($entity.Name)"
195+
} catch {
196+
Write-Warning "Failed to add scheduled task: $($_.Exception.Message)"
197+
Write-Information $_.InvocationInfo.PositionMessage
198+
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
199+
throw "Could not add task: $ErrorMessage"
171200
}
172201
}

0 commit comments

Comments
 (0)