azurepowershellmicrosoft-graph-apiazure-entra-id

Powershell script intended to pull all users' emails and manager's emails that belong to a specific security group in AD, is creating a blank csv


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


Solution

  • 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