Skip to content

Commit e2b462d

Browse files
authored
Merge pull request #508 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 2000fda + 10014b3 commit e2b462d

File tree

14 files changed

+592
-77
lines changed

14 files changed

+592
-77
lines changed

Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function Add-CIPPScheduledTask {
5656

5757
$propertiesToCheck = @('Webhook', 'Email', 'PSA')
5858
$PostExecutionObject = ($propertiesToCheck | Where-Object { $task.PostExecution.$_ -eq $true })
59-
$PostExecution = $PostExecutionObject ? ($PostExecutionObject -join ',') : ($Task.PostExecution.value -join ',')
59+
$PostExecution = $PostExecutionObject ? @($PostExecutionObject -join ',') : ($Task.PostExecution.value -join ',')
6060
$Parameters = [System.Collections.Hashtable]@{}
6161
foreach ($Key in $task.Parameters.PSObject.Properties.Name) {
6262
$Param = $task.Parameters.$Key
@@ -122,6 +122,10 @@ function Add-CIPPScheduledTask {
122122
$task.Recurrence.value
123123
}
124124

125+
if ($task.PSObject.Properties.Name -notcontains 'ScheduledTime') {
126+
$task | Add-Member -MemberType NoteProperty -Name 'ScheduledTime' -Value 0 -Force
127+
}
128+
125129
if ($DesiredStartTime) {
126130
try {
127131
# Parse the epoch time
@@ -186,6 +190,8 @@ function Add-CIPPScheduledTask {
186190
Results = 'Planned'
187191
AlertComment = [string]$task.AlertComment
188192
}
193+
194+
189195
# Always store DesiredStartTime if provided
190196
if ($DesiredStartTime) {
191197
$entity['DesiredStartTime'] = [string]$DesiredStartTime
@@ -205,22 +211,57 @@ function Add-CIPPScheduledTask {
205211
# Not a JSON object, ignore
206212
}
207213
}
214+
215+
if ($task.Trigger) {
216+
$entity.Trigger = [string]($task.Trigger | ConvertTo-Json -Compress)
217+
$TriggerType = $task.Trigger.Type.value ?? $task.Trigger.Type
218+
if ($TriggerType -eq 'DeltaQuery') {
219+
$Parameters = @{}
220+
if ($task.Trigger.WatchedAttributes -and ($task.Trigger.WatchedAttributes | Measure-Object).Count -gt 0) {
221+
$Parameters.'$select' = $task.Trigger.WatchedAttributes | ForEach-Object { $_.value ?? $_ } -join ','
222+
}
223+
if ($task.Trigger.ResourceFilter) {
224+
$Parameters.'$filter' = "id eq '" + $task.Trigger.ResourceFilter | ForEach-Object { $_.value ?? $_ } -join "' or id eq '"
225+
}
226+
$Resource = $task.Trigger.DeltaResource.value ?? $task.Trigger.DeltaResource
227+
228+
if ($entity.TenantGroup) {
229+
$tenantFilter = $entity.TenantGroup | ConvertFrom-Json
230+
}
231+
$DeltaQuery = @{
232+
TenantFilter = $tenantFilter
233+
Resource = $Resource
234+
Parameters = $Parameters
235+
PartitionKey = $RowKey
236+
}
237+
238+
try {
239+
$null = New-GraphDeltaQuery @DeltaQuery
240+
Write-Information "Created delta query for resource $($Resource)"
241+
} catch {
242+
Write-Warning "Failed to create delta query for resource $($Resource): $($_.Exception.Message)"
243+
}
244+
}
245+
}
246+
208247
if ($SyncType) {
209248
$entity.SyncType = $SyncType
210249
}
211250
try {
212251
Add-CIPPAzDataTableEntity @Table -Entity $entity -Force
213252
} catch {
214253
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
215-
return "Could not add task: $ErrorMessage"
254+
Write-Information $_.InvocationInfo.PositionMessage
255+
Write-Information ($entity | ConvertTo-Json)
256+
return "Error - Could not add task: $ErrorMessage"
216257
}
217258
Write-LogMessage -headers $Headers -API 'ScheduledTask' -message "Added task $($entity.Name) with ID $($entity.RowKey)" -Sev 'Info' -Tenant $tenantFilter
218259
return "Successfully added task: $($entity.Name)"
219260
}
220261
} catch {
221262
Write-Warning "Failed to add scheduled task: $($_.Exception.Message)"
222-
Write-Information $_.InvocationInfo.PositionMessage
223263
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
224-
throw "Could not add task: $ErrorMessage"
264+
#Write-Information ($Task | ConvertTo-Json)
265+
throw "Error - Could not add task: $ErrorMessage"
225266
}
226267
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
function Get-DeltaQueryResults {
2+
<#
3+
.SYNOPSIS
4+
Retrieves results for Delta Queries
5+
.DESCRIPTION
6+
This helper function modifies the results from Delta Query triggers based on specified properties.
7+
.PARAMETER Data
8+
The data containing Delta Query results. Use %triggerdata% from the scheduler to pass in the data.
9+
.PARAMETER Properties
10+
A comma-separated list of properties to include in the output. If not specified, all properties are returned.
11+
#>
12+
13+
[CmdletBinding()]
14+
param(
15+
[Parameter(Mandatory = $true)]
16+
$Data,
17+
[string]$Properties,
18+
[string]$TenantFilter,
19+
$Headers
20+
)
21+
22+
$Properties = $Properties -split ',' | ForEach-Object { $_.Trim() }
23+
if (!$Properties -or $Properties.Count -eq 0) {
24+
Write-Information 'No specific properties requested, returning all data.'
25+
Write-Information ($Data | ConvertTo-Json -Depth 10)
26+
return $Data
27+
}
28+
29+
$Data = $Data | Select-Object -Property $Properties
30+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
function Get-DeltaQueryUrl {
2+
<#
3+
.SYNOPSIS
4+
Retrieves the URL for Delta Queries
5+
.DESCRIPTION
6+
This helper function constructs the URL for Delta Query requests based on the resource and parameters.
7+
.PARAMETER TenantFilter
8+
The tenant to filter the query on.
9+
.PARAMETER PartitionKey
10+
The partition key for the delta query.
11+
#>
12+
13+
[CmdletBinding()]
14+
param(
15+
[Parameter(Mandatory = $true)]
16+
$TenantFilter,
17+
[Parameter(Mandatory = $true)]
18+
$PartitionKey
19+
)
20+
21+
$Table = Get-CIPPTable -TableName 'DeltaQueries'
22+
$DeltaQueryEntity = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq '$PartitionKey' and RowKey eq '$TenantFilter'"
23+
24+
if ($DeltaQueryEntity) {
25+
return $DeltaQueryEntity.DeltaUrl
26+
} else {
27+
throw "Delta Query not found for Tenant '$TenantFilter' and PartitionKey '$PartitionKey'."
28+
}
29+
}

Modules/CIPPCore/Public/DeltaQueries/New-GraphDeltaQuery.ps1

Lines changed: 123 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -13,87 +13,158 @@ function New-GraphDeltaQuery {
1313
Additional query parameters (e.g., $select, $filter, $top).
1414
.PARAMETER DeltaUrl
1515
Use this parameter to continue a delta query with a specific delta or next link.
16+
.FUNCTIONALITY
17+
Internal
1618
#>
1719
[CmdletBinding(DefaultParameterSetName = 'NewDeltaQuery')]
1820
param(
1921
[Parameter(Mandatory = $true, ParameterSetName = 'NewDeltaQuery')]
2022
[Parameter(Mandatory = $true, ParameterSetName = 'DeltaUrl')]
21-
[string]$TenantFilter,
23+
$TenantFilter,
2224

2325
[Parameter(ParameterSetName = 'NewDeltaQuery', Mandatory = $true)]
24-
[ValidateSet('users', 'groups', 'contacts', 'devices', 'applications', 'servicePrincipals', 'directoryObjects', 'administrativeUnits')]
26+
[ValidateSet('users', 'groups', 'contacts', 'orgContact', 'devices', 'applications', 'servicePrincipals', 'directoryObjects', 'directoryRole', 'administrativeUnits', 'oAuth2PermissionGrant')]
2527
[string]$Resource,
2628

2729
[Parameter(ParameterSetName = 'NewDeltaQuery', Mandatory = $false)]
2830
[hashtable]$Parameters = @{},
2931

3032
[Parameter(ParameterSetName = 'DeltaUrl', Mandatory = $true)]
31-
[string]$DeltaUrl
33+
[string]$DeltaUrl,
34+
35+
[Parameter(Mandatory = $false, ParameterSetName = 'NewDeltaQuery')]
36+
[Parameter(Mandatory = $true, ParameterSetName = 'DeltaUrl')]
37+
[string]$PartitionKey
3238
)
3339

34-
try {
35-
if ($DeltaUrl) {
36-
$GraphQuery = [System.UriBuilder]$DeltaUrl
40+
if ($TenantFilter -eq 'AllTenants' -or $TenantFilter.type -eq 'Group') {
41+
Write-Information 'Creating delta query for all tenants or tenant group.'
42+
if ($TenantFilter.type -eq 'group') {
43+
$Tenants = Expand-CIPPTenantGroups -TenantFilter $TenantFilter
3744
} else {
38-
$GraphQuery = [System.UriBuilder]('https://graph.microsoft.com/beta/{0}/delta' -f $Resource)
39-
$QueryParams = @{
40-
'$deltaToken' = 'latest'
45+
$Tenants = Get-Tenants -IncludeErrors
46+
}
47+
48+
if (!$PartitionKey) {
49+
$ParamJson = $Parameters | ConvertTo-Json -Depth 5 -Compress
50+
$PartitionKey = Get-StringHash -String ($Resource + $ParamJson)
51+
}
52+
# Prepare batch processing for all tenants
53+
$TenantBatch = $Tenants | ForEach-Object {
54+
[PSCustomObject]@{
55+
FunctionName = 'GraphDeltaQuery'
56+
TenantFilter = $_.defaultDomainName ?? $_.value
57+
Resource = $Resource
58+
Parameters = $Parameters
59+
PartitionKey = $PartitionKey
4160
}
42-
$ParamCollection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
61+
}
4362

44-
foreach ($key in $QueryParams.Keys) {
45-
if ($QueryParams[$key]) {
46-
$ParamCollection.Add($key, $QueryParams[$key])
47-
}
63+
$InputObject = @{
64+
Batch = @($TenantBatch)
65+
OrchestratorName = 'ProcessDeltaQueries'
66+
SkipLog = $true
67+
}
68+
Write-Information "Starting delta query orchestration for $($Tenants.Count) tenants."
69+
Write-Information "Orchestration Input: $($InputObject | ConvertTo-Json -Compress -Depth 5)"
70+
$Orchestration = Start-NewOrchestration -FunctionName CIPPOrchestrator -InputObject ($InputObject | ConvertTo-Json -Compress -Depth 5)
71+
72+
} else {
73+
$Table = Get-CIPPTable -TableName 'DeltaQueries'
74+
75+
if ($Parameters -and $Resource) {
76+
$ParamJson = $Parameters | ConvertTo-Json -Depth 5
77+
$ResourceHash = Get-StringHash -String ($Resource + $ParamJson)
78+
79+
$DeltaQuery = @{
80+
PartitionKey = $PartitionKey ?? $ResourceHash
81+
RowKey = $TenantFilter
82+
Resource = $Resource
83+
Parameters = [string]($Parameters | ConvertTo-Json -Depth 5 -Compress)
84+
DeltaUrl = $DeltaUrl
4885
}
86+
} elseif ($PartitionKey) {
87+
$DeltaQuery = Get-AzDataTableEntity @Table -Filter "PartitionKey eq '$PartitionKey' and RowKey eq '$TenantFilter'"
88+
}
4989

50-
foreach ($Item in ($Parameters.GetEnumerator() | Sort-Object -CaseSensitive -Property Key)) {
51-
if ($Item.Value -is [System.Boolean]) {
52-
$Item.Value = $Item.Value.ToString().ToLower()
90+
try {
91+
if ($DeltaUrl) {
92+
$GraphQuery = [System.UriBuilder]$DeltaUrl
93+
} else {
94+
$GraphQuery = [System.UriBuilder]('https://graph.microsoft.com/beta/{0}/delta' -f $Resource)
95+
$QueryParams = @{
96+
'$deltaToken' = 'latest'
5397
}
54-
if ($Item.Value) {
55-
$ParamCollection.Add($Item.Key, $Item.Value)
98+
$ParamCollection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
99+
100+
foreach ($key in $QueryParams.Keys) {
101+
if ($QueryParams[$key]) {
102+
$ParamCollection.Add($key, $QueryParams[$key])
103+
}
56104
}
57-
}
58-
$GraphQuery.Query = $ParamCollection.ToString()
59-
}
60-
61-
$allResults = [System.Collections.ArrayList]::new()
62-
$nextUrl = $GraphQuery.ToString()
63-
$deltaLink = $null
64-
65-
do {
66-
$response = New-GraphGetRequest -tenantid $TenantFilter -uri $nextUrl -ReturnRawResponse
67-
68-
if ($response.Content) {
69-
$content = $response.Content
70-
if ($content -is [string]) {
71-
$content = $content | ConvertFrom-Json
105+
106+
foreach ($Item in ($Parameters.GetEnumerator() | Sort-Object -CaseSensitive -Property Key)) {
107+
if ($Item.Value -is [System.Boolean]) {
108+
$Item.Value = $Item.Value.ToString().ToLower()
109+
}
110+
if ($Item.Value) {
111+
$ParamCollection.Add($Item.Key, $Item.Value)
112+
}
72113
}
114+
$GraphQuery.Query = $ParamCollection.ToString()
115+
}
116+
117+
$allResults = [System.Collections.ArrayList]::new()
118+
$nextUrl = $GraphQuery.ToString()
119+
$deltaLink = $null
73120

74-
# Add results from this page
75-
if ($content.value) {
76-
$allResults.AddRange($content.value)
121+
$DeltaError = $false
122+
do {
123+
try {
124+
$response = New-GraphGetRequest -tenantid $TenantFilter -uri $nextUrl -ReturnRawResponse -extraHeaders @{ Prefer = 'return=minimal' } -ErrorAction Stop
125+
if ($response.Content) {
126+
$content = $response.Content
127+
if ($content -is [string]) {
128+
$content = $content | ConvertFrom-Json
129+
}
130+
131+
# Add results from this page
132+
if ($content.value) {
133+
$allResults.AddRange($content.value)
134+
}
135+
136+
# Check for next page or delta link
137+
$nextUrl = $content.'@odata.nextLink'
138+
$deltaLink = $content.'@odata.deltaLink'
139+
}
140+
} catch {
141+
Write-Error "Error during Graph Delta Query request for tenant '$TenantFilter': $(Get-NormalizedError -Message $_.Exception.message)"
142+
$DeltaError = $true
77143
}
144+
} while ($nextUrl -and -not $deltaLink -and -not $DeltaError)
78145

79-
# Check for next page or delta link
80-
$nextUrl = $content.'@odata.nextLink'
81-
$deltaLink = $content.'@odata.deltaLink'
146+
if ($DeltaError) {
147+
throw "Delta Query failed for tenant '$TenantFilter'."
82148
}
83-
} while ($nextUrl -and -not $deltaLink)
149+
$DeltaQuery.RowKey = $TenantFilter
150+
$DeltaQuery.DeltaUrl = $deltaLink
84151

85-
# Return results with delta link for future queries
86-
$result = @{
87-
value = $allResults.ToArray()
88-
'@odata.deltaLink' = $deltaLink
89-
}
152+
# Return results with delta link for future queries
153+
$result = @{
154+
value = $allResults.ToArray()
155+
'@odata.deltaLink' = $deltaLink
156+
PartitionKey = $PartitionKey
157+
}
158+
# Save link to table
159+
Add-CIPPAzDataTableEntity @Table -Entity $DeltaQuery -Force
90160

91-
Write-Information "Delta Query completed for $Resource. Total items: $($allResults.Count)"
161+
Write-Information "Delta Query created for $($DeltaQuery.Resource). Total items: $($allResults.Count)"
92162

93-
# Always return full response with deltaLink
94-
return $result
95-
} catch {
96-
Write-Error "Failed to create Delta Query: $(Get-NormalizedError -Message $_.Exception.message)"
97-
Write-Warning $_.InvocationInfo.PositionMessage
163+
# Always return full response with deltaLink
164+
return $result
165+
} catch {
166+
Write-Error "Failed to create Delta Query: $(Get-NormalizedError -Message $_.Exception.message)"
167+
Write-Warning $_.InvocationInfo.PositionMessage
168+
}
98169
}
99170
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
function Push-GraphDeltaQuery {
2+
<#
3+
.FUNCTIONALITY
4+
Entrypoint
5+
#>
6+
[CmdletBinding()]
7+
param (
8+
$Item
9+
)
10+
11+
$Item = $Item | Select-Object -ExcludeProperty FunctionName | ConvertTo-Json -Depth 5 | ConvertFrom-Json -AsHashtable
12+
try {
13+
New-GraphDeltaQuery @Item
14+
} catch {
15+
Write-Error "Failed to create Delta Query: $(Get-NormalizedError -Message $_.Exception.message)"
16+
Write-Warning $_.InvocationInfo.PositionMessage
17+
}
18+
19+
}

0 commit comments

Comments
 (0)