Skip to content

Commit b7d1f66

Browse files
authored
Merge pull request KelvinTegelaar#940 from JohnDuprey/dev
Extension data sync
2 parents ca3b627 + d8121a7 commit b7d1f66

10 files changed

Lines changed: 304 additions & 18 deletions

Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ function Add-CIPPApplicationPermission {
66
$Tenantfilter
77
)
88
if ($ApplicationId -eq $ENV:ApplicationID -and $Tenantfilter -eq $env:TenantID) {
9-
return @('Cannot modify application permissions for CIPP-SAM on partner tenant')
9+
#return @('Cannot modify application permissions for CIPP-SAM on partner tenant')
10+
$RequiredResourceAccess = 'CIPPDefaults'
1011
}
1112
Set-Location (Get-Item $PSScriptRoot).FullName
1213
if ($RequiredResourceAccess -eq 'CIPPDefaults') {

Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ function Add-CIPPDelegatedPermission {
99
Set-Location (Get-Item $PSScriptRoot).FullName
1010

1111
if ($ApplicationId -eq $ENV:ApplicationID -and $Tenantfilter -eq $env:TenantID) {
12-
return @('Cannot modify delgated permissions for CIPP-SAM on partner tenant')
12+
#return @('Cannot modify delgated permissions for CIPP-SAM on partner tenant')
13+
$RequiredResourceAccess = 'CIPPDefaults'
1314
}
1415

1516
if ($RequiredResourceAccess -eq 'CIPPDefaults') {
1617
$RequiredResourceAccess = (Get-Content '.\SAMManifest.json' | ConvertFrom-Json).requiredResourceAccess
18+
$AdditionalPermissions = Get-Content '.\AdditionalPermissions.json' | ConvertFrom-Json
19+
$RequiredResourceAccess = $RequiredResourceAccess + ($AdditionalPermissions | Where-Object { $RequiredResourceAccess.resourceAppId -notcontains $_.resourceAppId })
1720
}
1821
$Translator = Get-Content '.\PermissionsTranslator.json' | ConvertFrom-Json
1922
$ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -tenantid $Tenantfilter -skipTokenCache $true
@@ -22,10 +25,17 @@ function Add-CIPPDelegatedPermission {
2225

2326
$CurrentDelegatedScopes = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals/$($ourSVCPrincipal.id)/oauth2PermissionGrants" -skipTokenCache $true -tenantid $Tenantfilter
2427

25-
foreach ($App in $requiredResourceAccess) {
28+
foreach ($App in $RequiredResourceAccess) {
2629
$svcPrincipalId = $ServicePrincipalList | Where-Object -Property AppId -EQ $App.resourceAppId
30+
$AdditionalScopes = ($AdditionalPermissions | Where-Object -Property resourceAppId -EQ $App.resourceAppId).resourceAccess
2731
if (!$svcPrincipalId) { continue }
28-
$NewScope = ($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value -join ' '
32+
if ($AdditionalScopes) {
33+
$NewScope = (($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value + $AdditionalScopes.id | Select-Object -Unique) -join ' '
34+
Write-Host "NEW SCOPE: $NewScope"
35+
} else {
36+
$NewScope = ($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value -join ' '
37+
}
38+
2939
$OldScope = ($CurrentDelegatedScopes | Where-Object -Property Resourceid -EQ $svcPrincipalId.id)
3040

3141
if (!$OldScope) {

Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ function Add-CIPPScheduledTask {
22
[CmdletBinding()]
33
param(
44
[pscustomobject]$Task,
5-
[bool]$Hidden
5+
[bool]$Hidden,
6+
[string]$SyncType = $null
67
)
78

89
$Table = Get-CIPPTable -TableName 'ScheduledTasks'
@@ -49,10 +50,13 @@ function Add-CIPPScheduledTask {
4950
Hidden = [bool]$Hidden
5051
Results = 'Planned'
5152
}
53+
if ($SyncType) {
54+
$entity.SyncType = $SyncType
55+
}
5256
try {
5357
Add-CIPPAzDataTableEntity @Table -Entity $entity -Force
5458
} catch {
5559
return "Could not add task: $($_.Exception.Message)"
5660
}
5761
return "Successfully added task: $($entity.Name)"
58-
}
62+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[
2+
{
3+
"resourceAppId": "00000003-0000-0ff1-ce00-000000000000",
4+
"resourceAccess": [{ "id": "AllProfiles.Manage", "type": "Scope" }]
5+
},
6+
{
7+
"resourceAppId": "fb78d390-0c51-40cd-8e17-fdbfab77341b",
8+
"resourceAccess": [
9+
{ "id": "AdminApi.AccessAsUser.All", "type": "Scope" },
10+
{ "id": "FfoPowerShell.AccessAsUser.All", "type": "Scope" },
11+
{ "id": "RemotePowerShell.AccessAsUser.All", "type": "Scope" },
12+
{ "id": "VivaFeatureAccessPolicy.Manage.All", "type": "Scope" }
13+
]
14+
}
15+
]

Modules/CIPPCore/Public/SAMManifest.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,7 @@
172172
{
173173
"resourceAppId": "00000003-0000-0ff1-ce00-000000000000",
174174
"resourceAccess": [
175-
{ "id": "56680e0d-d2a3-4ae1-80d8-3c4f2100e3d0", "type": "Scope" },
176-
{ "id": "ec4fc4c8-872e-442b-a2a2-d095575807b3", "type": "Scope" }
175+
{ "id": "56680e0d-d2a3-4ae1-80d8-3c4f2100e3d0", "type": "Scope" }
177176
]
178177
},
179178
{

Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,14 @@ function Set-CIPPSPOTenant {
5757
# Get property type
5858
$PropertyType = $Properties[$Property].GetType().Name
5959
if ($PropertyType -in $AllowedTypes) {
60-
if ($PropertyType -eq 'Boolean') { $Properties[$Property] = $Properties[$Property].ToString().ToLower() }
60+
if ($PropertyType -eq 'Boolean') {
61+
$PropertyToSet = $Properties[$Property].ToString().ToLower()
62+
} else {
63+
$PropertyToSet = $Properties[$Property]
64+
}
6165
$xml = @"
6266
<SetProperty Id="$x" ObjectPathId="110" Name="$Property">
63-
<Parameter Type="Boolean">$($Properties[$Property])</Parameter>
67+
<Parameter Type="Boolean">$($PropertyToSet)</Parameter>
6468
</SetProperty>
6569
"@
6670
$SetProperty.Add($xml)
@@ -85,4 +89,4 @@ function Set-CIPPSPOTenant {
8589
New-GraphPostRequest -scope "$AdminURL/.default" -tenantid $TenantFilter -Uri "$AdminURL/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' -AddedHeaders $AdditionalHeaders
8690
}
8791
}
88-
}
92+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
function Push-ExtensionSyncData {
2+
param(
3+
$TenantFilter,
4+
$Extension
5+
)
6+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
function Register-CIPPExtensionScheduledTasks {
2+
Param(
3+
[switch]$Reschedule
4+
)
5+
6+
# get extension configuration and mappings table
7+
$Table = Get-CIPPTable -TableName Extensionsconfig
8+
$Config = ((Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -ea stop)
9+
$MappingsTable = Get-CIPPTable -TableName CippMapping
10+
11+
# Get existing scheduled usertasks
12+
$ScheduledTasksTable = Get-CIPPTable -TableName ScheduledTasks
13+
$ScheduledTasks = Get-CIPPAzDataTableEntity @ScheduledTasksTable -Filter 'Hidden eq true' | Where-Object { $_.Command -match 'Sync-CippExtensionData' }
14+
$Tenants = Get-Tenants -IncludeErrors
15+
16+
$Extensions = @('Hudu')
17+
18+
foreach ($Extension in $Extensions) {
19+
$ExtensionConfig = $Config.$Extension
20+
if ($ExtensionConfig.Enabled -eq $true) {
21+
$Mappings = Get-CIPPAzDataTableEntity @MappingsTable -Filter "PartitionKey eq '$($Extension)Mapping'"
22+
$FieldMapping = Get-CIPPAzDataTableEntity @MappingsTable -Filter "PartitionKey eq '$($Extension)FieldMapping'"
23+
$FieldSync = @{}
24+
$SyncTypes = [System.Collections.Generic.List[string]]::new()
25+
26+
foreach ($Mapping in $FieldMapping) {
27+
$FieldSync[$Mapping.RowKey] = !([string]::IsNullOrEmpty($Mapping.IntegrationId))
28+
}
29+
30+
$SyncTypes.Add('Overview')
31+
32+
if ($FieldSync.Users) {
33+
$SyncTypes.Add('Users')
34+
$SyncTypes.Add('Mailboxes')
35+
}
36+
if ($FieldSync.Devices) {
37+
$SyncTypes.Add('Devices')
38+
}
39+
40+
foreach ($Mapping in $Mappings) {
41+
$Tenant = $Tenants | Where-Object { $_.customerId -eq $Mapping.RowKey }
42+
43+
foreach ($SyncType in $SyncTypes) {
44+
$ExistingTask = $ScheduledTasks | Where-Object { $_.Tenant -eq $Tenant.defaultDomainName -and $_.SyncType -eq $SyncType }
45+
if (!$ExistingTask -or $Reschedule.IsPresent) {
46+
$unixtime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds
47+
$Task = @{
48+
Name = "Extension Sync - $SyncType"
49+
Command = @{
50+
value = 'Sync-CippExtensionData'
51+
label = 'Sync-CippExtensionData'
52+
}
53+
Parameters = @{
54+
TenantFilter = $Tenant.defaultDomainName
55+
SyncType = $SyncType
56+
}
57+
Recurrence = '1d'
58+
ScheduledTime = $unixtime
59+
TenantFilter = $Tenant.defaultDomainName
60+
}
61+
if ($ExistingTask) {
62+
$Task.RowKey = $ExistingTask.RowKey
63+
}
64+
$null = Add-CIPPScheduledTask -Task $Task -hidden $true -SyncType $SyncType
65+
}
66+
}
67+
}
68+
}
69+
}
70+
71+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
function Sync-CippExtensionData {
2+
<#
3+
.FUNCTIONALITY
4+
Internal
5+
#>
6+
[CmdletBinding()]
7+
param(
8+
$TenantFilter,
9+
$SyncType
10+
)
11+
12+
$Table = Get-CIPPTable -TableName ExtensionSync
13+
$Extensions = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq '$($SyncType)'"
14+
$LastSync = $Extensions | Where-Object { $_.RowKey -eq $TenantFilter }
15+
$CacheTable = Get-CIPPTable -tablename 'CacheExtensionSync'
16+
17+
if (!$LastSync) {
18+
$LastSync = @{
19+
PartitionKey = $SyncType
20+
RowKey = $TenantFilter
21+
Status = 'Not Synced'
22+
Error = ''
23+
LastSync = 'Never'
24+
}
25+
Add-CIPPAzDataTableEntity @Table -Entity $LastSync
26+
}
27+
28+
try {
29+
switch ($SyncType) {
30+
'Overview' {
31+
# Build bulk requests array.
32+
[System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @(
33+
@{
34+
id = 'TenantDetails'
35+
method = 'GET'
36+
url = '/organization'
37+
},
38+
@{
39+
id = 'AllRoles'
40+
method = 'GET'
41+
url = '/directoryRoles?$top=999'
42+
},
43+
@{
44+
id = 'Domains'
45+
method = 'GET'
46+
url = '/domains$top=999'
47+
},
48+
@{
49+
id = 'Licenses'
50+
method = 'GET'
51+
url = '/subscribedSkus?$top=999'
52+
},
53+
@{
54+
id = 'Groups'
55+
method = 'GET'
56+
url = '/groups?$top=999&$select=id,createdDateTime,displayName,description,mail,mailEnabled,mailNickname,resourceProvisioningOptions,securityEnabled,visibility,organizationId,onPremisesSamAccountName,membershipRule,grouptypes,onPremisesSyncEnabled,resourceProvisioningOptions,userPrincipalName'
57+
},
58+
@{
59+
id = 'ConditionalAccess'
60+
method = 'GET'
61+
url = '/identity/conditionalAccess/policies'
62+
},
63+
@{
64+
id = 'SecureScoreControlProfiles'
65+
method = 'GET'
66+
url = '/security/secureScoreControlProfiles?$top=999'
67+
},
68+
@{
69+
id = 'Subscriptions'
70+
method = 'GET'
71+
url = '/directory/subscriptions?$top=999'
72+
}
73+
)
74+
75+
$SingleGraphQueries = @(@{
76+
id = 'SecureScore'
77+
graphRequest = @{
78+
uri = 'https://graph.microsoft.com/beta/security/secureScores?$top=1'
79+
noPagination = $true
80+
}
81+
})
82+
}
83+
'Users' {
84+
[System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @(
85+
@{
86+
id = 'Users'
87+
method = 'GET'
88+
url = '/users?$top=999&$select=id,accountEnabled,businessPhones,city,createdDateTime,companyName,country,department,displayName,faxNumber,givenName,isResourceAccount,jobTitle,mail,mailNickname,mobilePhone,onPremisesDistinguishedName,officeLocation,onPremisesLastSyncDateTime,otherMails,postalCode,preferredDataLocation,preferredLanguage,proxyAddresses,showInAddressList,state,streetAddress,surname,usageLocation,userPrincipalName,userType,assignedLicenses,onPremisesSyncEnabled'
89+
}
90+
)
91+
}
92+
'Devices' {
93+
[System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @(
94+
@{
95+
id = 'Devices'
96+
method = 'GET'
97+
url = '/deviceManagement/managedDevices?$top=999'
98+
},
99+
@{
100+
id = 'DeviceCompliancePolicies'
101+
method = 'GET'
102+
url = '/deviceManagement/deviceCompliancePolicies'
103+
},
104+
@{
105+
id = 'DeviceApps'
106+
method = 'GET'
107+
url = '/deviceAppManagement/mobileApps'
108+
}
109+
)
110+
}
111+
'Mailboxes' {
112+
$Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses,WhenSoftDeleted,IsInactiveMailbox'
113+
$ExoRequest = @{
114+
tenantid = $TenantFilter
115+
cmdlet = 'Get-Mailbox'
116+
cmdParams = @{}
117+
Select = $Select
118+
}
119+
$Mailboxes = (New-ExoRequest @ExoRequest) | Select-Object id, ExchangeGuid, ArchiveGuid, WhenSoftDeleted, @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } },
120+
121+
@{ Name = 'displayName'; Expression = { $_.'DisplayName' } },
122+
@{ Name = 'primarySmtpAddress'; Expression = { $_.'PrimarySMTPAddress' } },
123+
@{ Name = 'recipientType'; Expression = { $_.'RecipientType' } },
124+
@{ Name = 'recipientTypeDetails'; Expression = { $_.'RecipientTypeDetails' } },
125+
@{ Name = 'AdditionalEmailAddresses'; Expression = { ($_.'EmailAddresses' | Where-Object { $_ -clike 'smtp:*' }).Replace('smtp:', '') -join ', ' } }
126+
127+
$Entity = @{
128+
PartitionKey = $TenantFilter
129+
SyncType = 'Mailboxes'
130+
RowKey = 'Mailboxes'
131+
Data = [string]($Mailboxes | ConvertTo-Json -Depth 10 -Compress)
132+
}
133+
Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force
134+
}
135+
}
136+
137+
if ($TenantRequests) {
138+
try {
139+
$TenantResults = New-GraphBulkRequest -Requests $TenantRequests -tenantid $TenantFilter
140+
} catch {
141+
Throw "Failed to fetch bulk company data: $_"
142+
}
143+
144+
if ($SingleGraphQueries) {
145+
foreach ($SingleGraphQuery in $SingleGraphQueries) {
146+
$Request = $SingleGraphQuery.graphRequest
147+
$Data = New-GraphGetRequest @Request -tenantid $TenantFilter
148+
$Entity = @{
149+
PartitionKey = $TenantFilter
150+
SyncType = $SyncType
151+
RowKey = $SingleGraphQuery.id
152+
Data = [string]($Data | ConvertTo-Json -Depth 10 -Compress)
153+
}
154+
Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force
155+
}
156+
}
157+
158+
$TenantResults | Select-Object id, body | ForEach-Object {
159+
$Entity = @{
160+
PartitionKey = $TenantFilter
161+
RowKey = $_.id
162+
SyncType = $SyncType
163+
Data = [string]($_.body.value | ConvertTo-Json -Depth 10 -Compress)
164+
}
165+
Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force
166+
}
167+
}
168+
$LastSync.LastSync = [datetime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')
169+
$LastSync.Status = 'Completed'
170+
$LastSync.Error = ''
171+
} catch {
172+
$LastSync.Status = 'Failed'
173+
$LastSync.Error = [string](Get-CippException -Exception $_ | ConvertTo-Json -Compress)
174+
throw "Failed to sync data: $($_.Exception.Message)"
175+
} finally {
176+
Add-CIPPAzDataTableEntity @Table -Entity $LastSync -Force
177+
}
178+
}

0 commit comments

Comments
 (0)