1+ if (-not $script :TenantGroupsCache ) {
2+ $script :TenantGroupsCache = @ {
3+ Groups = $null
4+ Members = $null
5+ LastRefresh = $null
6+ MembersByGroup = $null # Dictionary: GroupId -> members array
7+ }
8+ }
9+
10+ # Result cache: keyed by "GroupId|TenantFilter|Dynamic"
11+ if (-not $script :TenantGroupsResultCache ) {
12+ $script :TenantGroupsResultCache = @ {}
13+ }
14+
115function Get-TenantGroups {
216 <#
317 . SYNOPSIS
418 Get tenant groups
519 . DESCRIPTION
6- Get tenant groups from Azure Table Storage
20+ Get tenant groups from Azure Table Storage with performance optimizations
21+ using script-scoped caches and in-memory indexing
722 . PARAMETER GroupId
823 The group id to filter on
924 . PARAMETER TenantFilter
1025 The tenant filter to apply to get the groups for a specific tenant
26+ . PARAMETER Dynamic
27+ Filter to only dynamic groups
1128 #>
1229 [CmdletBinding ()]
1330 param (
@@ -16,8 +33,14 @@ function Get-TenantGroups {
1633 [switch ]$Dynamic
1734 )
1835
19- $GroupTable = Get-CippTable - tablename ' TenantGroups'
20- $MembersTable = Get-CippTable - tablename ' TenantGroupMembers'
36+ # Build cache key for result caching
37+ $CacheKey = " $GroupId |$TenantFilter |$ ( $Dynamic.IsPresent ) "
38+
39+ # Return cached result if available
40+ if ($script :TenantGroupsResultCache.ContainsKey ($CacheKey )) {
41+ Write-Verbose " Returning cached result for: $CacheKey "
42+ return $script :TenantGroupsResultCache [$CacheKey ]
43+ }
2144
2245 # Early exit if specific GroupId requested but not allowed
2346 if ($GroupId -and $script :CippAllowedGroupsStorage -and $script :CippAllowedGroupsStorage.Value ) {
@@ -26,6 +49,34 @@ function Get-TenantGroups {
2649 }
2750 }
2851
52+ # Load table data into cache if not already loaded
53+ if (-not $script :TenantGroupsCache.Groups -or -not $script :TenantGroupsCache.Members ) {
54+ Write-Verbose ' Loading TenantGroups and TenantGroupMembers tables into cache'
55+
56+ $GroupTable = Get-CippTable - tablename ' TenantGroups'
57+ $MembersTable = Get-CippTable - tablename ' TenantGroupMembers'
58+
59+ $GroupTable.Filter = " PartitionKey eq 'TenantGroup'"
60+
61+ # Load all groups and members once
62+ $script :TenantGroupsCache.Groups = @ (Get-CIPPAzDataTableEntity @GroupTable )
63+ $script :TenantGroupsCache.Members = @ (Get-CIPPAzDataTableEntity @MembersTable )
64+ $script :TenantGroupsCache.LastRefresh = Get-Date
65+
66+ # Build MembersByGroup index: GroupId -> array of member objects
67+ $script :TenantGroupsCache.MembersByGroup = @ {}
68+ foreach ($Member in $script :TenantGroupsCache.Members ) {
69+ $GId = $Member.GroupId
70+ if (-not $script :TenantGroupsCache.MembersByGroup.ContainsKey ($GId )) {
71+ $script :TenantGroupsCache.MembersByGroup [$GId ] = [System.Collections.Generic.List [object ]]::new()
72+ }
73+ $script :TenantGroupsCache.MembersByGroup [$GId ].Add($Member )
74+ }
75+
76+ Write-Verbose " Cache loaded: $ ( $script :TenantGroupsCache.Groups.Count ) groups, $ ( $script :TenantGroupsCache.Members.Count ) members"
77+ }
78+
79+ # Get tenants (already cached and fast per requirements)
2980 if ($TenantFilter -and $TenantFilter -ne ' allTenants' ) {
3081 $TenantParams = @ {
3182 TenantFilter = $TenantFilter
@@ -38,51 +89,74 @@ function Get-TenantGroups {
3889 }
3990 $Tenants = Get-Tenants @TenantParams
4091
92+ $TenantByCustomerId = @ {}
93+ foreach ($Tenant in $Tenants ) {
94+ $TenantByCustomerId [$Tenant.customerId ] = $Tenant
95+ }
96+
97+ $Groups = $script :TenantGroupsCache.Groups
98+
4199 if ($Dynamic.IsPresent ) {
42- $GroupTable.Filter = " PartitionKey eq 'TenantGroup' and GroupType eq 'dynamic'"
43- } else {
44- $GroupTable.Filter = " PartitionKey eq 'TenantGroup'"
100+ $Groups = $Groups | Where-Object { $_.GroupType -eq ' dynamic' }
45101 }
46102
47103 if ($GroupId ) {
48- $Groups = Get-CIPPAzDataTableEntity @GroupTable - Filter " RowKey eq '$GroupId '"
49- $AllMembers = Get-CIPPAzDataTableEntity @MembersTable - Filter " GroupId eq '$GroupId '"
50- } else {
51- $Groups = Get-CIPPAzDataTableEntity @GroupTable
52- $AllMembers = Get-CIPPAzDataTableEntity @MembersTable
104+ $Groups = $Groups | Where-Object { $_.RowKey -eq $GroupId }
105+ }
53106
54- # Filter to allowed groups if restrictions apply
55- if ($script :CippAllowedGroupsStorage -and $script :CippAllowedGroupsStorage.Value ) {
56- $Groups = $Groups | Where-Object { $script :CippAllowedGroupsStorage.Value -contains $_.RowKey }
57- }
107+ if ($script :CippAllowedGroupsStorage -and $script :CippAllowedGroupsStorage.Value ) {
108+ $Groups = $Groups | Where-Object { $script :CippAllowedGroupsStorage.Value -contains $_.RowKey }
58109 }
59110
60- if (! $Groups ) {
111+ if (! $Groups -or $Groups.Count -eq 0 ) {
112+ $script :TenantGroupsResultCache [$CacheKey ] = @ ()
61113 return @ ()
62114 }
63115
116+ # Process results based on TenantFilter
64117 if ($TenantFilter -and $TenantFilter -ne ' allTenants' ) {
118+ # Return simplified group list for specific tenant
65119 $Results = [System.Collections.Generic.List [PSCustomObject ]]::new()
66- $Memberships = $AllMembers | Where-Object { $_.customerId -eq $Tenants.customerId }
67- foreach ($Group in $Memberships ) {
68- $Group = $Groups | Where-Object { $_.RowKey -eq $Group.GroupId }
69- if ($Group ) {
70- $Results.Add ([PSCustomObject ]@ {
71- Id = $Group.RowKey
72- Name = $Group.Name
73- Description = $Group.Description
74- })
120+ $TargetCustomerId = $Tenants.customerId
121+
122+ foreach ($Group in $Groups ) {
123+ $GroupMembers = $script :TenantGroupsCache.MembersByGroup [$Group.RowKey ]
124+
125+ if ($GroupMembers ) {
126+ # Check if this group has the target tenant as a member
127+ $HasTenant = $false
128+ foreach ($Member in $GroupMembers ) {
129+ if ($Member.customerId -eq $TargetCustomerId ) {
130+ $HasTenant = $true
131+ break
132+ }
133+ }
134+
135+ if ($HasTenant ) {
136+ $Results.Add ([PSCustomObject ]@ {
137+ Id = $Group.RowKey
138+ Name = $Group.Name
139+ Description = $Group.Description
140+ })
141+ }
75142 }
76143 }
77- return $Results | Sort-Object Name
144+
145+ $FinalResults = $Results | Sort-Object Name
146+ $script :TenantGroupsResultCache [$CacheKey ] = $FinalResults
147+ return $FinalResults
78148 } else {
149+ # Return full group details with members
79150 $Results = [System.Collections.Generic.List [PSCustomObject ]]::new()
151+
80152 foreach ($Group in $Groups ) {
81- $Members = $AllMembers | Where-Object { $_ .GroupId -eq $Group.RowKey }
153+ $GroupMembers = $script :TenantGroupsCache .MembersByGroup [ $Group.RowKey ]
82154 $MembersList = [System.Collections.Generic.List [hashtable ]]::new()
83- if ($Members ) {
84- foreach ($Member in $Members ) {
85- $Tenant = $Tenants | Where-Object { $Member.customerId -eq $_.customerId }
155+
156+ if ($GroupMembers ) {
157+ foreach ($Member in $GroupMembers ) {
158+ # Use indexed lookup instead of Where-Object
159+ $Tenant = $TenantByCustomerId [$Member.customerId ]
86160 if ($Tenant ) {
87161 $MembersList.Add (@ {
88162 customerId = $Tenant.customerId
@@ -95,6 +169,7 @@ function Get-TenantGroups {
95169 } else {
96170 $SortedMembers = @ ()
97171 }
172+
98173 $Results.Add ([PSCustomObject ]@ {
99174 Id = $Group.RowKey
100175 Name = $Group.Name
@@ -105,6 +180,9 @@ function Get-TenantGroups {
105180 Members = @ ($SortedMembers )
106181 })
107182 }
108- return $Results | Sort-Object Name
183+
184+ $FinalResults = $Results | Sort-Object Name
185+ $script :TenantGroupsResultCache [$CacheKey ] = $FinalResults
186+ return $FinalResults
109187 }
110188}
0 commit comments