Skip to content

Commit f0297f5

Browse files
authored
Merge pull request #149 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 0a296f9 + e1a26bc commit f0297f5

File tree

7 files changed

+265
-17
lines changed

7 files changed

+265
-17
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
function Invoke-CustomDataSync {
2+
param(
3+
$TenantFilter
4+
)
5+
6+
$Table = Get-CIPPTable -TableName CustomDataMappings
7+
$CustomData = Get-CIPPAzDataTableEntity @Table
8+
9+
$Mappings = $CustomData | ForEach-Object {
10+
$Mapping = $_.JSON | ConvertFrom-Json
11+
$Mapping
12+
}
13+
14+
Write-Information "Found $($Mappings.Count) Custom Data mappings"
15+
$Mappings = $Mappings | Where-Object { $_.sourceType.value -eq 'extensionSync' -and $_.tenantFilter.value -contains $TenantFilter -or $_.tenantFilter.value -contains 'AllTenants' }
16+
17+
if ($Mappings.Count -eq 0) {
18+
Write-Warning "No Custom Data mappings found for tenant $TenantFilter"
19+
return
20+
}
21+
22+
Write-Information "Getting cached data for tenant $TenantFilter"
23+
$Cache = Get-ExtensionCacheData -TenantFilter $TenantFilter
24+
$BulkRequests = [System.Collections.Generic.List[object]]::new()
25+
$DirectoryObjectQueries = [System.Collections.Generic.List[object]]::new()
26+
$SyncConfigs = foreach ($Mapping in $Mappings) {
27+
$SyncConfig = [PSCustomObject]@{
28+
Dataset = $Mapping.extensionSyncDataset.value
29+
DatasetConfig = $Mapping.extensionSyncDataset.addedFields
30+
DirectoryObjectType = $Mapping.directoryObjectType.value
31+
ExtensionSyncProperty = $Mapping.extensionSyncProperty.value
32+
CustomDataAttribute = $Mapping.customDataAttribute.value
33+
CustomDataAttributeConfig = $Mapping.customDataAttribute.addedFields
34+
}
35+
36+
switch ($SyncConfig.DirectoryObjectType) {
37+
'user' {
38+
$Query = @{
39+
id = 'user'
40+
url = 'users?$select=id,userPrincipalName,displayName&$count=true&$top=999'
41+
method = 'GET'
42+
}
43+
}
44+
}
45+
$DirectoryObjectQueries.Add($Query)
46+
$SyncConfig
47+
}
48+
49+
Write-Information "Getting directory objects for tenant $TenantFilter"
50+
#Write-Information ($DirectoryObjectQueries | ConvertTo-Json -Depth 10)
51+
$AllDirectoryObjects = New-GraphBulkRequest -tenantid $TenantFilter -Requests @($DirectoryObjectQueries)
52+
53+
foreach ($SyncConfig in $SyncConfigs) {
54+
Write-Warning "Processing Custom Data mapping for $($Mapping.customDataAttribute.value)"
55+
Write-Information ($SyncConfig | ConvertTo-Json -Depth 10)
56+
$Rows = $Cache.$($SyncConfig.Dataset)
57+
if (!$Rows) {
58+
Write-Warning "No data found for dataset $($SyncConfig.Dataset)"
59+
continue
60+
}
61+
$SourceMatchProperty = $SyncConfig.DatasetConfig.sourceMatchProperty
62+
$DestinationMatchProperty = $SyncConfigs.DatasetConfig.destinationMatchProperty
63+
$CustomDataAttribute = $SyncConfig.CustomDataAttribute
64+
$ExtensionSyncProperty = $SyncConfig.ExtensionSyncProperty
65+
$DatasetConfig = $SyncConfig.DatasetConfig
66+
67+
$DirectoryObjects = ($AllDirectoryObjects | Where-Object { $_.id -eq $SyncConfig.DirectoryObjectType }).body.value
68+
69+
switch ($SyncConfig.DirectoryObjectType) {
70+
'user' {
71+
$url = 'users'
72+
}
73+
}
74+
75+
foreach ($Row in $Rows) {
76+
#Write-Warning 'Processing row'
77+
#Write-Information ($Row | ConvertTo-Json -Depth 10)
78+
#Write-Host "Comparing $SourceMatchProperty $($Row.$SourceMatchProperty) to $($DirectoryObjects.Count) directory objects on $DestinationMatchProperty"
79+
$DirectoryObject = $DirectoryObjects | Where-Object { $_.$DestinationMatchProperty -eq $Row.$SourceMatchProperty }
80+
if (!$DirectoryObject) {
81+
Write-Warning "No directory object found for $($Row.$SourceMatchProperty)"
82+
}
83+
if ($DirectoryObject) {
84+
$ObjectUrl = "$($url)/$($DirectoryObject.id)"
85+
86+
if ($DatasetConfig.type -eq 'object') {
87+
if ($CustomDataAttribute -match '\.') {
88+
$Props = @($CustomDataAttribute -split '\.')
89+
$Body = [PSCustomObject]@{
90+
$Props[0] = [PSCustomObject]@{
91+
$Props[1] = $Row.$ExtensionSyncProperty
92+
}
93+
}
94+
} else {
95+
$Body = [PSCustomObject]@{
96+
$CustomDataAttribute = @($Row.$ExtensionSyncProperty)
97+
}
98+
}
99+
} elseif ($DatasetConfig.type -eq 'array') {
100+
101+
$Data = foreach ($Entry in $Row.$ExtensionSyncProperty) {
102+
if ($DatasetConfig.storeAs -eq 'json') {
103+
$Entry | ConvertTo-Json -Depth 5 -Compress
104+
} else {
105+
$Entry
106+
}
107+
}
108+
109+
$Body = [PSCustomObject]@{
110+
$CustomDataAttribute = @($Data)
111+
}
112+
}
113+
114+
$BulkRequests.Add([PSCustomObject]@{
115+
id = $DirectoryObject.$DestinationMatchProperty
116+
url = $ObjectUrl
117+
method = 'PATCH'
118+
headers = @{
119+
'Content-Type' = 'application/json'
120+
}
121+
body = $Body.PSObject.Copy()
122+
})
123+
}
124+
}
125+
}
126+
127+
#Write-Host ($BulkRequests | ConvertTo-Json -Depth 10)
128+
if ($BulkRequests.Count -gt 0) {
129+
Write-Information "Sending $($BulkRequests.Count) requests to Graph API"
130+
$Responses = New-GraphBulkRequest -tenantid $TenantFilter -Requests @($BulkRequests)
131+
if ($Responses | Where-Object { $_.statusCode -ne 204 }) {
132+
Write-Warning 'Some requests failed'
133+
Write-Information ($Responses | Where-Object { $_.statusCode -ne 204 } | Select-Object -Property id, statusCode | ConvertTo-Json)
134+
}
135+
}
136+
}

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

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ function Invoke-ExecCustomData {
1010

1111
$Action = $Request.Query.Action ?? $Request.Body.Action
1212
$CustomDataTable = Get-CippTable -TableName 'CustomData'
13+
$CustomDataMappingsTable = Get-CippTable -TableName 'CustomDataMappings'
1314

1415
Write-Information "Executing action '$Action'"
1516

@@ -231,6 +232,19 @@ function Invoke-ExecCustomData {
231232
try {
232233
$Uri = "https://graph.microsoft.com/beta/applications(appId='$($env:ApplicationId)')/extensionProperties"
233234
$DirectoryExtensions = New-GraphGetRequest -uri $Uri -AsApp $true -NoAuthCheck $true -tenantid $env:TenantID
235+
$Existing = Get-CIPPAzDataTableEntity @CustomDataTable -Filter "PartitionKey eq 'DirectoryExtension'"
236+
237+
foreach ($DirectoryExtension in $DirectoryExtensions) {
238+
if ($Existing -match $DirectoryExtension.name) {
239+
continue
240+
}
241+
$Entity = @{
242+
PartitionKey = 'DirectoryExtension'
243+
RowKey = $DirectoryExtension.name
244+
JSON = [string](ConvertTo-Json $DirectoryExtension -Compress -Depth 5)
245+
}
246+
Add-CIPPAzDataTableEntity @CustomDataTable -Entity $Entity -Force
247+
}
234248

235249
$Body = @{
236250
Results = @($DirectoryExtensions)
@@ -281,8 +295,9 @@ function Invoke-ExecCustomData {
281295
$Entity = @{
282296
PartitionKey = 'DirectoryExtension'
283297
RowKey = $Response.name
284-
JSON = [string](ConvertFrom-Json $Response -Compress -Depth 5)
298+
JSON = [string](ConvertTo-Json $Response -Compress -Depth 5)
285299
}
300+
Add-CIPPAzDataTableEntity @CustomDataTable -Entity $Entity -Force
286301
} catch {
287302
$Body = @{
288303
Results = @(
@@ -341,6 +356,64 @@ function Invoke-ExecCustomData {
341356
Results = @($AvailableAttributes)
342357
}
343358
}
359+
'ListMappings' {
360+
try {
361+
$Mappings = Get-CIPPAzDataTableEntity @CustomDataMappingsTable | ForEach-Object {
362+
$Mapping = $_.JSON | ConvertFrom-Json -AsHashtable
363+
[PSCustomObject]@{
364+
id = $_.RowKey
365+
tenant = $Mapping.tenantFilter.label
366+
sourceDataset = $Mapping.sourceDataset.label
367+
}
368+
}
369+
$Body = @{
370+
Results = @($Mappings)
371+
}
372+
} catch {
373+
$Body = @{
374+
Results = @(
375+
@{
376+
state = 'error'
377+
resultText = "Failed to retrieve mappings: $($_.Exception.Message)"
378+
}
379+
)
380+
}
381+
}
382+
}
383+
'AddMapping' {
384+
try {
385+
$Mapping = $Request.Body.Mapping
386+
if (!$Mapping) {
387+
throw 'Mapping data is missing in the request body.'
388+
}
389+
$id = [Guid]::NewGuid().ToString()
390+
$Entity = @{
391+
PartitionKey = 'Mapping'
392+
RowKey = $id
393+
JSON = [string]($Mapping | ConvertTo-Json -Depth 5 -Compress)
394+
}
395+
396+
Add-CIPPAzDataTableEntity @CustomDataMappingsTable -Entity $Entity -Force
397+
Register-CIPPExtensionScheduledTasks
398+
399+
$Body = @{
400+
Results = @{
401+
state = 'success'
402+
resultText = "Mapping with ID '$($id)' added successfully."
403+
}
404+
}
405+
} catch {
406+
$Body = @{
407+
Results = @(
408+
@{
409+
state = 'error'
410+
resultText = "Failed to add mapping: $($_.Exception.Message)"
411+
}
412+
)
413+
}
414+
}
415+
}
416+
344417
default {
345418
$Body = @{
346419
Results = @(

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecRemoveMailboxRule.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Function Invoke-ExecRemoveMailboxRule {
1515
Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message 'Accessed this API' -Sev 'Debug'
1616

1717
# Interact with the query or body of the request
18-
$TenantFilter = $Request.Query.TenantFilter ?? $Request.Query.TenantFilter
18+
$TenantFilter = $Request.Query.TenantFilter ?? $Request.Body.TenantFilter
1919
$RuleName = $Request.Query.ruleName ?? $Request.Body.ruleName
2020
$RuleId = $Request.Query.ruleId ?? $Request.Body.ruleId
2121
$Username = $Request.Query.userPrincipalName ?? $Request.Body.userPrincipalName

Modules/CIPPCore/Public/Get-CippCustomDataAttributes.ps1

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,18 @@ function Get-CippCustomDataAttributes {
1818
if ($Type -eq 'SchemaExtension') {
1919
$Name = $CustomData.id
2020
foreach ($TargetObject in $CustomData.targetTypes) {
21-
[PSCustomObject]@{
22-
name = $Name
23-
type = $Type
24-
targetObject = $TargetObject.ToLower()
25-
properties = $CustomData.properties
21+
foreach ($Property in $CustomData.properties) {
22+
[PSCustomObject]@{
23+
name = '{0}.{1}' -f $Name, $Property.name
24+
type = $Type
25+
targetObject = $TargetObject
26+
dataType = $Property.type
27+
isMultiValued = $false
28+
}
2629
}
2730
}
2831
} elseif ($Type -eq 'DirectoryExtension') {
29-
$Name = $CustomData.RowKey
32+
$Name = $CustomDataEntity.RowKey
3033
foreach ($TargetObject in $CustomData.targetObjects) {
3134
[PSCustomObject]@{
3235
name = $Name

Modules/CippExtensions/Public/Extension Functions/Push-CippExtensionData.ps1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,9 @@ function Push-CippExtensionData {
1414
Invoke-HuduExtensionSync -Configuration $Config -TenantFilter $TenantFilter
1515
}
1616
}
17+
'CustomData' {
18+
Write-Host 'Perfoming Custom Data Extension Sync...'
19+
Invoke-CustomDataSync -TenantFilter $TenantFilter
20+
}
1721
}
1822
}

Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
function Register-CIPPExtensionScheduledTasks {
2-
Param(
2+
param(
33
[switch]$Reschedule
44
)
55

@@ -14,13 +14,39 @@ function Register-CIPPExtensionScheduledTasks {
1414
$PushTasks = Get-CIPPAzDataTableEntity @ScheduledTasksTable -Filter 'Hidden eq true' | Where-Object { $_.Command -match 'Push-CippExtensionData' }
1515
$Tenants = Get-Tenants -IncludeErrors
1616

17-
$Extensions = @('Hudu', 'NinjaOne')
17+
$Extensions = @('Hudu', 'NinjaOne', 'CustomData')
1818
$MappedTenants = [System.Collections.Generic.List[string]]::new()
1919
foreach ($Extension in $Extensions) {
2020
$ExtensionConfig = $Config.$Extension
21-
if ($ExtensionConfig.Enabled -eq $true) {
22-
$Mappings = Get-CIPPAzDataTableEntity @MappingsTable -Filter "PartitionKey eq '$($Extension)Mapping'"
23-
$FieldMapping = Get-CIPPAzDataTableEntity @MappingsTable -Filter "PartitionKey eq '$($Extension)FieldMapping'"
21+
if ($ExtensionConfig.Enabled -eq $true -or $Extension -eq 'CustomData') {
22+
if ($Extension -eq 'CustomData') {
23+
$CustomDataMappingTable = Get-CIPPTable -TableName CustomDataMappings
24+
$Mappings = Get-CIPPAzDataTableEntity @CustomDataMappingTable | ForEach-Object {
25+
$Mapping = $_.JSON | ConvertFrom-Json
26+
if ($Mapping.sourceType -eq 'extensionSync') {
27+
$TenantMappings = if ($Mapping.tenantFilter.value -contains 'AllTenants') {
28+
$Tenants
29+
} else {
30+
foreach ($TenantMapping in $TenantMappings) {
31+
$TenantMapping | Where-Object { $_.customerId -eq $Mapping.tenantFilter.value -or $_.defaultDomainName -eq $Mapping.tenantFilter.value }
32+
}
33+
}
34+
foreach ($TenantMapping in $TenantMappings) {
35+
[pscustomobject]@{
36+
RowKey = $TenantMapping.customerId
37+
}
38+
}
39+
}
40+
} | Sort-Object -Property RowKey -Unique
41+
42+
if (($Mappings | Measure-Object).Count -eq 0) {
43+
Write-Warning 'No tenants found for CustomData extension'
44+
continue
45+
}
46+
} else {
47+
$Mappings = Get-CIPPAzDataTableEntity @MappingsTable -Filter "PartitionKey eq '$($Extension)Mapping'"
48+
$FieldMapping = Get-CIPPAzDataTableEntity @MappingsTable -Filter "PartitionKey eq '$($Extension)FieldMapping'"
49+
}
2450
$FieldSync = @{}
2551
$SyncTypes = [System.Collections.Generic.List[string]]::new()
2652

Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,20 +151,26 @@ function Sync-CippExtensionData {
151151
)
152152
}
153153
'Mailboxes' {
154-
$Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses,WhenSoftDeleted,IsInactiveMailbox,ProhibitSendQuota,ProhibitSendReceiveQuota,LitigationHoldEnabled,InPlaceHolds,HiddenFromAddressListsEnabled'
154+
$Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses,WhenSoftDeleted,IsInactiveMailbox,ForwardingSmtpAddress,DeliverToMailboxAndForward,ForwardingAddress,HiddenFromAddressListsEnabled,ExternalDirectoryObjectId,MessageCopyForSendOnBehalfEnabled,MessageCopyForSentAsEnabled'
155155
$ExoRequest = @{
156156
tenantid = $TenantFilter
157157
cmdlet = 'Get-Mailbox'
158158
cmdParams = @{}
159159
Select = $Select
160160
}
161-
$Mailboxes = (New-ExoRequest @ExoRequest) | Select-Object id, ExchangeGuid, ArchiveGuid, WhenSoftDeleted, ProhibitSendQuota, ProhibitSendReceiveQuota, LitigationHoldEnabled, InplaceHolds, HiddenFromAddressListsEnabled, @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } },
162-
161+
$Mailboxes = (New-ExoRequest @ExoRequest) | Select-Object id, ExchangeGuid, ArchiveGuid, WhenSoftDeleted, @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } },
163162
@{ Name = 'displayName'; Expression = { $_.'DisplayName' } },
164163
@{ Name = 'primarySmtpAddress'; Expression = { $_.'PrimarySMTPAddress' } },
165164
@{ Name = 'recipientType'; Expression = { $_.'RecipientType' } },
166165
@{ Name = 'recipientTypeDetails'; Expression = { $_.'RecipientTypeDetails' } },
167-
@{ Name = 'AdditionalEmailAddresses'; Expression = { ($_.'EmailAddresses' | Where-Object { $_ -clike 'smtp:*' }).Replace('smtp:', '') -join ', ' } }
166+
@{ Name = 'AdditionalEmailAddresses'; Expression = { ($_.'EmailAddresses' | Where-Object { $_ -clike 'smtp:*' }).Replace('smtp:', '') -join ', ' } },
167+
@{Name = 'ForwardingSmtpAddress'; Expression = { $_.'ForwardingSmtpAddress' -replace 'smtp:', '' } },
168+
@{Name = 'InternalForwardingAddress'; Expression = { $_.'ForwardingAddress' } },
169+
DeliverToMailboxAndForward,
170+
HiddenFromAddressListsEnabled,
171+
ExternalDirectoryObjectId,
172+
MessageCopyForSendOnBehalfEnabled,
173+
MessageCopyForSentAsEnabled
168174

169175
$Entity = @{
170176
PartitionKey = $TenantFilter

0 commit comments

Comments
 (0)