I used to have a Powershell script that used the AzureAD module to list all users and their managers in active directory, and then a separate script for getting all users of a security group. Now that the AzureAD module is deprecated I'm trying to use a script that uses Microsoft Graph to both get a list of users of that security group and also show their managers email, but I keep getting a blank CSV, despite the group having 30 or so members.
I initially sent individual Powershell commands to get a list of all User Ids belonging to the group successfully, but anytime I try to use a script that exports to a csv, it's a blank 0KB file
Import-Module Microsoft.Graph
Connect-MgGraph -Scopes "User.Read.All", "Group.Read.All", "User.ReadBasic.All"
$securityGroupName = "redgroup"
$csvFilePath = "C:\Users\bruce\Documents\redgroup_userlist.csv"
$group = Get-MgGroup -Filter "displayName eq '$securityGroupName'"
if ($null -eq $group) {
Write-Host "Group '$securityGroupName' not found." -ForegroundColor Red
exit
}
$groupMembers = Get-MgGroupMember -GroupId $group.Id -All -Property "id,displayName,userPrincipalName"
$userDetails = @()
foreach ($member in $groupMembers) {
if ($member.'@odata.type' -eq "#microsoft.graph.user") {
$user = Get-MgUser -UserId $member.Id -Property "displayName,userPrincipalName,manager"
$managerEmail = $null
if ($user.Manager) {
$manager = Get-MgUser -UserId $user.Manager.Id -Property "userPrincipalName"
$managerEmail = $manager.UserPrincipalName
}
$userDetails += [PSCustomObject]@{
"UserDisplayName" = $user.DisplayName
"UserEmail" = $user.UserPrincipalName
"ManagerEmail" = $managerEmail
}
}
}
$userDetails | Export-Csv -Path $csvFilePath -NoTypeInformation
Disconnect-MgGraph
Write-Host "Red Group's manager list created at $csvFilePath" -ForegroundColor Green
The issue with your code is that you're missing a reference for the .AdditionalProperties
property, $member.'@odata.type'
should be $member.AdditionalProperties.'@odata.type'
, due to this, your first if
condition is never met and nothing gets added to your array.
You should also avoid the use of @()
and +=
, you can simply assign the output from loop expression to a variable as demonstrated below, see Array addition for details.
In summary, this is how your code should look:
$groupMembers = Get-MgGroupMember -GroupId $group.Id -All -Property 'id,displayName,userPrincipalName'
$userDetails = foreach ($member in $groupMembers) {
if ($member.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.user') {
$user = Get-MgUser -UserId $member.Id -Property 'displayName,userPrincipalName,manager'
$managerEmail = $null
if ($user.Manager) {
$manager = Get-MgUser -UserId $user.Manager.Id -Property 'userPrincipalName'
$managerEmail = $manager.UserPrincipalName
}
[PSCustomObject]@{
UserDisplayName = $user.DisplayName
UserEmail = $user.UserPrincipalName
ManagerEmail = $managerEmail
}
}
}
$userDetails | Export-Csv -Path $csvFilePath -NoTypeInformation
There is a better way to get this information if you make a direct call to the List group members endpoint, you can use the OData cast to get group members that are of type microsoft.graph.user
, this is more efficient than filtering client side. You can also use $expand=manager
to get the manager's UserPrincipalName
so your 3 Graph calls get reduced to a single one.
This is how the code would look:
# same code as before and assuming we already have `$group.Id`
$uri = 'v1.0/groups/{0}/members/microsoft.graph.user?$select=displayName,userPrincipalName&$expand=manager' -f $group.Id
$userDetails = do {
$response = Invoke-MgGraphRequest GET $uri
$uri = $response.'@odata.nextLink'
foreach ($member in $response.value) {
[pscustomobject]@{
UserDisplayName = $member['displayName']
UserEmail = $member['userPrincipalName']
ManagerEmail = $member['manager']['mail']
}
}
}
while ($uri)
$userDetails | Export-Csv -Path $csvFilePath -NoTypeInformation