Let me preface this by saying I’m not a PowerShell expert by any means. I’ve written the script below, which gives the correct results, but the performance is abysmal. It is taking > 5 minutes to execute, and I know there is a more efficient way to achieve the same result. I’m hoping for some help in refactoring this script to optimize performance as much as possible.
Summary of requirement: Pull a list of users from MS Graph who are in these 4 security groups (they may be a member in more than one group) and add a column to the user details output to indicate a "Yes/No" on whether they are a member of each of the 4 groups respectively.
Desired Output:
DisplayName : John Smith Id : 1234567890 Mail : [email protected] UserPrincipalName : [email protected] JobTitle : Manager Group1 : Yes Group2 : Yes Group3 : No Group4 : No
Import-Module Microsoft.Graph.Users.Actions
#Group List
$groups = @{
GroupIds = @(
'123456789' #Group 1
'987654321' #Group 2
'154637485' #Group 3
'856453756' #Group 4
)
}
Connect-MgGraph -Scopes 'User.Read.All', 'Group.ReadWrite.All'
$tmSku = Get-MgSubscribedSku -All | Where-Object SkuPartNumber -EQ 'SKU1'
$tmUsers = Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($tmsku.SkuId) )" -ConsistencyLevel eventual -CountVariable tmlicensedUserCount -All | Select-Object 'DisplayName', 'Id', 'Mail', 'UserPrincipalName', 'JobTitle'
$tmUsers | Add-Member -NotePropertyName Group1 -NotePropertyValue No
$tmUsers | Add-Member -NotePropertyName Group2 -NotePropertyValue No
$tmUsers | Add-Member -NotePropertyName Group3 -NotePropertyValue No
$tmUsers | Add-Member -NotePropertyName Group4 -NotePropertyValue No
Write-Host "Found $tmlicensedUserCount users."
foreach ($teammemberuser in $tmUsers) {
$groupList = Confirm-MgUserMemberGroup -UserId $teammemberuser.Id -BodyParameter $groups
if ($groupList -contains '123456789') {
$teammemberuser.'Group1' = 'Yes'
}
if ($groupList -contains '987654321') {
$teammemberuser.'Group2' = 'Yes'
}
if ($groupList -contains '154637485') {
$teammemberuser.'Group3' = 'Yes'
}
if ($groupList -contains '856453756') {
$teammemberuser.'Group4' = 'Yes'
}
}
Thanks in advance for any assistance!
2
Answers
There are a few improvements you could make to make your code more efficient.
The key points are:
Add-Member
at all costs, attaching note properties to an existing object is already expensive and this cmdlet makes it even more expensive-Filter
condition, and then per each user, you’re making a Graph call to check if they’re members of the groups.Instead, make a single call to Graph, get the users that meet your
-Filter
but also get their membership with-ExpandProperty memberOf
. You can then use that membership (the.MemberOf
property) to do the checks.-Select
to reduce the amount of properties you want Graph to return.Adding this as a new answer as I think the previous one is worth keeping for future readers. The issue with the previous answer as we have learnt with OP (see extensive comment section) is that for some undocumented reason and, perhaps a bug, using
$expand=memberOf
and$expand=transitiveMemberOf
(see Properties inuser resource type
) don’t bring the user’s complete membership so this answer approaches the problem a different way while trying to keep it efficient.The main idea, as explained in the previous answer, is to try and reduce the number of Graph calls as much as possible. This is the main reason why your current code is slow. The approach of this answer is to query the groups and get the
transitiveMembers
that are users (transitiveMembers/microsoft.graph.user
) where those users have the target license assigned (assignedLicenses/any(e: e/skuId eq xxx-xx-xxx-xxx)
) and, for each user, project theirId
($select=id
).Then, we follow with the same approach as in your code, get all users having the assigned license but for the comparison we can check if each user exists in the
HashShet<T>
containing all membersId
of each group.For reference, the group members query might be doable with
Get-MgGroupMember
that should make the code less extensive but I personally don’t use the cmdlets in the Graph Module except forInvoke-MgGraphRequest
so I honestly have no idea if it’s possible and how to do it with that cmdlet.