Skip to content

Commit b30b34b

Browse files
authored
Merge pull request #506 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 5832e5d + 49103d1 commit b30b34b

File tree

4 files changed

+238
-26
lines changed

4 files changed

+238
-26
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
function Invoke-ExecRunTenantGroupRule {
2+
<#
3+
.SYNOPSIS
4+
Execute tenant group dynamic rules immediately
5+
.DESCRIPTION
6+
This function executes dynamic tenant group rules for immediate membership updates
7+
.FUNCTIONALITY
8+
Entrypoint,AnyTenant
9+
.ROLE
10+
Tenant.Groups.ReadWrite
11+
#>
12+
[CmdletBinding()]
13+
param($Request, $TriggerMetadata)
14+
15+
$GroupId = $Request.Body.groupId ?? $Request.Query.groupId
16+
17+
try {
18+
$GroupTable = Get-CippTable -tablename 'TenantGroups'
19+
$Group = Get-CIPPAzDataTableEntity @GroupTable -Filter "PartitionKey eq 'TenantGroup' and RowKey eq '$GroupId'"
20+
21+
if (-not $Group) { $Body = @{ Results = 'Group not found' } }
22+
23+
$UpdateResult = Update-CIPPDynamicTenantGroups -GroupId $GroupId
24+
$Body = @{ Results = "Dynamic rules executed successfully for group '$($Group.Name)'. Members added: $($UpdateResult.MembersAdded), Members removed: $($UpdateResult.MembersRemoved)" }
25+
26+
return ([HttpResponseContext]@{
27+
StatusCode = [HttpStatusCode]::OK
28+
Body = $Body
29+
})
30+
} catch {
31+
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
32+
Write-LogMessage -API 'TenantGroups' -message "Failed to execute tenant group rules: $ErrorMessage" -sev Error
33+
$Body = @{ Results = "Failed to execute dynamic rules: $ErrorMessage" }
34+
return ([HttpResponseContext]@{
35+
StatusCode = [HttpStatusCode]::InternalServerError
36+
Body = $Body
37+
})
38+
}
39+
}

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

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ function Invoke-ExecTenantGroup {
1919
$groupName = $Request.Body.groupName
2020
$groupDescription = $Request.Body.groupDescription
2121
$members = $Request.Body.members
22+
$groupType = $Request.Body.groupType ?? 'static'
23+
$dynamicRules = $Request.Body.dynamicRules
24+
$ruleLogic = $Request.Body.ruleLogic ?? 'and'
2225

2326
switch ($Action) {
2427
'AddEdit' {
@@ -32,46 +35,65 @@ function Invoke-ExecTenantGroup {
3235
if ($groupDescription) {
3336
$GroupEntity.Description = $groupDescription
3437
}
38+
$GroupEntity.GroupType = $groupType
39+
if ($groupType -eq 'dynamic' -and $dynamicRules) {
40+
$GroupEntity.DynamicRules = "$($dynamicRules | ConvertTo-Json -depth 100 -Compress)"
41+
$GroupEntity | Add-Member -NotePropertyName 'RuleLogic' -NotePropertyValue $ruleLogic -Force
42+
} else {
43+
$GroupEntity | Add-Member -NotePropertyName 'RuleLogic' -NotePropertyValue $null -Force
44+
}
3545
Add-CIPPAzDataTableEntity @Table -Entity $GroupEntity -Force
3646
} else {
3747
$GroupEntity = @{
3848
PartitionKey = 'TenantGroup'
3949
RowKey = $groupId
4050
Name = $groupName
4151
Description = $groupDescription
52+
GroupType = $groupType
53+
}
54+
if ($groupType -eq 'dynamic' -and $dynamicRules) {
55+
$GroupEntity.DynamicRules = "$($dynamicRules | ConvertTo-Json -depth 100 -Compress)"
56+
$GroupEntity.RuleLogic = $ruleLogic
4257
}
4358
Add-CIPPAzDataTableEntity @Table -Entity $GroupEntity -Force
4459
}
4560

46-
$CurrentMembers = Get-CIPPAzDataTableEntity @MembersTable -Filter "PartitionKey eq 'Member' and GroupId eq '$groupId'"
47-
61+
# Handle members based on group type
4862
$Adds = [System.Collections.Generic.List[string]]::new()
4963
$Removes = [System.Collections.Generic.List[string]]::new()
50-
# Add members
51-
foreach ($member in $members) {
52-
if ($CurrentMembers) {
53-
$CurrentMember = $CurrentMembers | Where-Object { $_.customerId -eq $member.value }
54-
if ($CurrentMember) {
55-
continue
64+
65+
if ($groupType -eq 'static') {
66+
# Static group - manage members manually
67+
$CurrentMembers = Get-CIPPAzDataTableEntity @MembersTable -Filter "PartitionKey eq 'Member' and GroupId eq '$groupId'"
68+
69+
# Add members
70+
foreach ($member in $members) {
71+
if ($CurrentMembers) {
72+
$CurrentMember = $CurrentMembers | Where-Object { $_.customerId -eq $member.value }
73+
if ($CurrentMember) {
74+
continue
75+
}
5676
}
77+
$MemberEntity = @{
78+
PartitionKey = 'Member'
79+
RowKey = '{0}-{1}' -f $groupId, $member.value
80+
GroupId = $groupId
81+
customerId = $member.value
82+
}
83+
Add-CIPPAzDataTableEntity @MembersTable -Entity $MemberEntity -Force
84+
$Adds.Add('Added member {0}' -f $member.label)
5785
}
58-
$MemberEntity = @{
59-
PartitionKey = 'Member'
60-
RowKey = '{0}-{1}' -f $groupId, $member.value
61-
GroupId = $groupId
62-
customerId = $member.value
63-
}
64-
Add-CIPPAzDataTableEntity @MembersTable -Entity $MemberEntity -Force
65-
$Adds.Add('Added member {0}' -f $member.label)
66-
}
6786

68-
if ($CurrentMembers) {
69-
foreach ($CurrentMember in $CurrentMembers) {
70-
if ($members.value -notcontains $CurrentMember.customerId) {
71-
Remove-AzDataTableEntity @MembersTable -Entity $CurrentMember -Force
72-
$Removes.Add('Removed member {0}' -f $CurrentMember.customerId)
87+
if ($CurrentMembers) {
88+
foreach ($CurrentMember in $CurrentMembers) {
89+
if ($members.value -notcontains $CurrentMember.customerId) {
90+
Remove-AzDataTableEntity @MembersTable -Entity $CurrentMember -Force
91+
$Removes.Add('Removed member {0}' -f $CurrentMember.customerId)
92+
}
7393
}
7494
}
95+
} elseif ($groupType -eq 'dynamic') {
96+
$Adds.Add('Dynamic group updated. Rules will be processed on next scheduled run.')
7597
}
7698
$Results.Add(@{
7799
resultText = "Group '$groupName' saved successfully"

Modules/CIPPCore/Public/Functions/Get-TenantGroups.ps1

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,13 @@ function Get-TenantGroups {
7777
$SortedMembers = @()
7878
}
7979
$Results.Add([PSCustomObject]@{
80-
Id = $Group.RowKey
81-
Name = $Group.Name
82-
Description = $Group.Description
83-
Members = @($SortedMembers)
80+
Id = $Group.RowKey
81+
Name = $Group.Name
82+
Description = $Group.Description
83+
GroupType = $Group.GroupType ?? 'static'
84+
RuleLogic = $Group.RuleLogic ?? 'and'
85+
DynamicRules = $Group.DynamicRules ? ( $(@($Group.DynamicRules | ConvertFrom-Json)) ) : @()
86+
Members = @($SortedMembers)
8487
})
8588
}
8689
return $Results | Sort-Object Name
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
function Update-CIPPDynamicTenantGroups {
2+
<#
3+
.SYNOPSIS
4+
Update dynamic tenant groups based on their rules
5+
.DESCRIPTION
6+
This function processes dynamic tenant group rules and updates membership accordingly
7+
.PARAMETER GroupId
8+
The specific group ID to update. If not provided, all dynamic groups will be updated
9+
.FUNCTIONALITY
10+
Internal
11+
#>
12+
[CmdletBinding()]
13+
param(
14+
[string]$GroupId
15+
)
16+
17+
try {
18+
$GroupTable = Get-CippTable -tablename 'TenantGroups'
19+
$MembersTable = Get-CippTable -tablename 'TenantGroupMembers'
20+
21+
if ($GroupId) {
22+
$DynamicGroups = Get-CIPPAzDataTableEntity @GroupTable -Filter "PartitionKey eq 'TenantGroup' and RowKey eq '$GroupId'"
23+
} else {
24+
$DynamicGroups = Get-CIPPAzDataTableEntity @GroupTable -Filter "PartitionKey eq 'TenantGroup' and GroupType eq 'dynamic'"
25+
}
26+
27+
if (-not $DynamicGroups) {
28+
Write-LogMessage -API 'TenantGroups' -message 'No dynamic groups found to process' -sev Info
29+
return @{ MembersAdded = 0; MembersRemoved = 0; GroupsProcessed = 0 }
30+
}
31+
32+
$AllTenants = Get-Tenants -IncludeErrors
33+
$TotalMembersAdded = 0
34+
$TotalMembersRemoved = 0
35+
$GroupsProcessed = 0
36+
37+
foreach ($Group in $DynamicGroups) {
38+
try {
39+
Write-LogMessage -API 'TenantGroups' -message "Processing dynamic group: $($Group.Name)" -sev Info
40+
$Rules = $Group.DynamicRules | ConvertFrom-Json
41+
# Build a single Where-Object string for AND logic
42+
$WhereConditions = foreach ($Rule in $Rules) {
43+
$Property = $Rule.property
44+
$Operator = $Rule.operator
45+
$Value = $Rule.value
46+
47+
switch ($Property) {
48+
'delegatedAccessStatus' {
49+
"`$_.delegatedPrivilegeStatus -$Operator '$($Value.value)'"
50+
}
51+
'availableLicense' {
52+
if ($Operator -in @('in', 'notin')) {
53+
$arrayValues = if ($Value -is [array]) { $Value.guid } else { @($Value.guid) }
54+
$arrayAsString = $arrayValues | ForEach-Object { "'$_'" }
55+
"(`$_.skuId | Where-Object { `$_ -in @($($arrayAsString -join ', ')) }).Count -gt 0"
56+
} else {
57+
"`$_.skuId -contains '$($Value.guid)'"
58+
}
59+
}
60+
'availableServicePlan' {
61+
if ($Operator -in @('in', 'notin')) {
62+
$arrayValues = if ($Value -is [array]) { $Value.value } else { @($Value.value) }
63+
$arrayAsString = $arrayValues | ForEach-Object { "'$_'" }
64+
"(`$_.servicePlans | Where-Object { `$_ -in @($($arrayAsString -join ', ')) }).Count -gt 0"
65+
} else {
66+
"`$_.servicePlans -contains '$($Value.value)'"
67+
}
68+
}
69+
default {
70+
Write-LogMessage -API 'TenantGroups' -message "Unknown property type: $Property" -sev Warning
71+
$null
72+
}
73+
}
74+
75+
}
76+
$TenantObj = $AllTenants | ForEach-Object {
77+
$LicenseInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/subscribedSkus' -TenantId $_.defaultDomainName
78+
$SKUId = $LicenseInfo.SKUId
79+
$ServicePlans = (Get-CIPPTenantCapabilities -TenantFilter $_.defaultDomainName).psobject.properties.name
80+
[pscustomobject]@{
81+
customerId = $_.customerId
82+
defaultDomainName = $_.defaultDomainName
83+
displayName = $_.displayName
84+
skuId = $SKUId
85+
servicePlans = $ServicePlans
86+
delegatedPrivilegeStatus = $_.delegatedPrivilegeStatus
87+
}
88+
}
89+
# Combine all conditions with the specified logic (AND or OR)
90+
$LogicOperator = if ($Group.RuleLogic -eq 'or') { ' -or ' } else { ' -and ' }
91+
$WhereString = $WhereConditions -join $LogicOperator
92+
$ScriptBlock = [ScriptBlock]::Create($WhereString)
93+
$MatchingTenants = $TenantObj | Where-Object $ScriptBlock
94+
95+
$CurrentMembers = Get-CIPPAzDataTableEntity @MembersTable -Filter "PartitionKey eq 'Member' and GroupId eq '$($Group.RowKey)'"
96+
$CurrentMemberIds = $CurrentMembers.customerId
97+
$NewMemberIds = $MatchingTenants.customerId
98+
99+
$ToAdd = $NewMemberIds | Where-Object { $_ -notin $CurrentMemberIds }
100+
$ToRemove = $CurrentMemberIds | Where-Object { $_ -notin $NewMemberIds }
101+
102+
foreach ($TenantId in $ToAdd) {
103+
$TenantInfo = $AllTenants | Where-Object { $_.customerId -eq $TenantId }
104+
$MemberEntity = @{
105+
PartitionKey = 'Member'
106+
RowKey = '{0}-{1}' -f $Group.RowKey, $TenantId
107+
GroupId = $Group.RowKey
108+
customerId = "$TenantId"
109+
}
110+
Add-CIPPAzDataTableEntity @MembersTable -Entity $MemberEntity -Force
111+
Write-LogMessage -API 'TenantGroups' -message "Added tenant '$($TenantInfo.displayName)' to dynamic group '$($Group.Name)'" -sev Info
112+
$TotalMembersAdded++
113+
}
114+
115+
foreach ($TenantId in $ToRemove) {
116+
$TenantInfo = $AllTenants | Where-Object { $_.customerId -eq $TenantId }
117+
$MemberToRemove = $CurrentMembers | Where-Object { $_.customerId -eq $TenantId }
118+
if ($MemberToRemove) {
119+
Remove-AzDataTableEntity @MembersTable -Entity $MemberToRemove -Force
120+
Write-LogMessage -API 'TenantGroups' -message "Removed tenant '$($TenantInfo.displayName)' from dynamic group '$($Group.Name)'" -sev Info
121+
$TotalMembersRemoved++
122+
}
123+
}
124+
125+
$GroupsProcessed++
126+
Write-LogMessage -API 'TenantGroups' -message "Group '$($Group.Name)' updated: +$($ToAdd.Count) members, -$($ToRemove.Count) members" -sev Info
127+
128+
} catch {
129+
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
130+
Write-LogMessage -API 'TenantGroups' -message "Failed to process group '$($Group.Name)': $ErrorMessage" -sev Error
131+
}
132+
}
133+
134+
Write-LogMessage -API 'TenantGroups' -message "Dynamic tenant group update completed. Groups processed: $GroupsProcessed, Members added: $TotalMembersAdded, Members removed: $TotalMembersRemoved" -sev Info
135+
136+
return @{
137+
MembersAdded = $TotalMembersAdded
138+
MembersRemoved = $TotalMembersRemoved
139+
GroupsProcessed = $GroupsProcessed
140+
}
141+
142+
} catch {
143+
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
144+
Write-LogMessage -API 'TenantGroups' -message "Failed to update dynamic tenant groups: $ErrorMessage" -sev Error
145+
throw
146+
}
147+
}
148+

0 commit comments

Comments
 (0)