Introduction

Managing groups in Entra ID (formerly Azure Active Directory) through the Azure Portal works fine for a handful of changes, but quickly becomes tedious at scale. Whether you’re onboarding a batch of new employees, restructuring departments, or cleaning up stale groups, PowerShell and the Microsoft Graph module give you the tools to do this reliably and repeatably.

This article walks through the practical essentials: installing the module, connecting to Graph, creating and managing groups, and bulk-assigning users from a CSV.

Prerequisites

Before you start, make sure you have:

  • PowerShell 5.1 or PowerShell 7+ installed
  • An Entra ID account with at least the Groups Administrator role
  • Internet access to connect to Microsoft Graph

Installing the Microsoft Graph PowerShell Module

The Microsoft Graph PowerShell SDK replaces the older AzureAD and MSOnline modules, which are now deprecated. Install it from the PowerShell Gallery:

Install-Module Microsoft.Graph -Scope CurrentUser

If you only need Group management, you can install just the relevant sub-module to save time:

Install-Module Microsoft.Graph.Groups -Scope CurrentUser

Connecting to Microsoft Graph

You’ll need to authenticate and specify the scopes your script requires. For group management, you need Group.ReadWrite.All:

Connect-MgGraph -Scopes "Group.ReadWrite.All", "User.Read.All"

This will open a browser window for interactive sign-in. For unattended or scheduled scripts, use a service principal with a certificate or client secret instead.

Creating a New Security Group

Creating a group is straightforward with New-MgGroup:

$group = New-MgGroup -DisplayName "IT-Automation-Team" `
    -MailEnabled:$false `
    -SecurityEnabled:$true `
    -MailNickname "IT-Automation-Team" `
    -Description "Group for IT automation team members"

Write-Output "Created group: $($group.Id)"

Note: set MailEnabled to $false for a pure security group. For a Microsoft 365 group (Teams, SharePoint etc.), set MailEnabled:$true and add "unified" to the GroupTypes parameter.

Adding Members to a Group

Once you have the group ID, you can add users by their object ID:

$groupId = "your-group-object-id"
$userId  = "user-object-id"

New-MgGroupMember -GroupId $groupId -DirectoryObjectId $userId

To look up a user by UPN first:

$user = Get-MgUser -Filter "userPrincipalName eq '[email protected]'"
New-MgGroupMember -GroupId $groupId -DirectoryObjectId $user.Id

Bulk-Adding Users from a CSV

A common real-world scenario is onboarding a batch of users from a spreadsheet. Save the UPNs in a CSV like this:

UserPrincipalName
[email protected]
[email protected]
[email protected]

Then run:

$groupId = "your-group-object-id"
$users   = Import-Csv -Path "C:\users.csv"

foreach ($entry in $users) {
    try {
        $user = Get-MgUser -Filter "userPrincipalName eq '$($entry.UserPrincipalName)'"
        if ($user) {
            New-MgGroupMember -GroupId $groupId -DirectoryObjectId $user.Id
            Write-Output "Added: $($entry.UserPrincipalName)"
        } else {
            Write-Warning "User not found: $($entry.UserPrincipalName)"
        }
    } catch {
        Write-Warning "Failed to add $($entry.UserPrincipalName): $_"
    }
}

Removing Stale Members

To audit and remove members who no longer belong in a group, compare current members against an approved list:

$groupId      = "your-group-object-id"
$approvedUPNs = @("[email protected]", "[email protected]")

$members = Get-MgGroupMember -GroupId $groupId -All

foreach ($member in $members) {
    $user = Get-MgUser -UserId $member.Id
    if ($approvedUPNs -notcontains $user.UserPrincipalName) {
        Remove-MgGroupMemberByRef -GroupId $groupId -DirectoryObjectId $member.Id
        Write-Output "Removed: $($user.UserPrincipalName)"
    }
}

Wrapping Up

The Microsoft Graph PowerShell module makes Entra ID group management both scriptable and auditable. Whether you’re running these scripts manually, scheduling them with Task Scheduler, or triggering them from a GitHub Actions workflow, the pattern is consistent: connect to Graph, query or mutate the resource, handle errors gracefully.

A good next step is wrapping these snippets into reusable functions and storing them in a private module or a GitHub repository — making your runbooks repeatable and version-controlled.