Least-Privilege User Management in Microsoft Entra ID Using Administrative Units
Securely automating accounts enable/disable with Microsoft Graph PowerShell.

If you've ever had to manually enable or disable a batch of user accounts in Microsoft Entra ID, for example, a group of exam accounts at the start or end of term, you'll know how quickly it becomes tedious. Logging into the Microsoft Entra admin centre, finding each user, toggling the sign-in status, and repeating the process over and over. It's manageable for a couple of users, but once you're dealing with 50+ accounts regularly, it makes much more sense to let PowerShell handle it.
In this post, I'll walk through an interactive script that connects to Microsoft Graph and lets you choose whether to enable or disable accounts. You can then target either a full Entra ID group or an individual user.
I'll also go through the permissions required to run it and how you can secure it properly using the Principle of Least Privilege (PoLP) and Administrative Units (AUs), so that access is kept as tightly controlled as possible.
The Problem
The main problem this script solves is managing the lifecycle of user accounts, whether that’s in bulk or just for a single user. A few real-world situations where this tends to come up are:
Exam or lab accounts at schools should be active only during exam periods and disabled for the rest of the year.
Seasonal or contract staff, where access needs to be switched off between engagements instead of deleting and recreating the accounts each time.
One-off requests, like a user going on extended leave or a compromised account that needs to be disabled quickly.
You can do all of this through the Microsoft Entra ID portal, but it doesn’t scale very well once you’re doing it regularly or across a larger number of accounts.
This script solves that by giving you a simple menu-driven interface. From there, you can choose to enable or disable accounts and target either a full Entra ID group for bulk changes or just a single user when needed.
Prerequisites
Install the Microsoft Graph PowerShell module
If you haven't already, open a PowerShell session and run:
Install-Module Microsoft.Graph -Scope CurrentUser
Technically, you don’t need the entire module for this task. The script only relies on a few sub-modules:
Microsoft.Graph.AuthenticationMicrosoft.Graph.GroupsMicrosoft.Graph.Users
However, installing the parent Microsoft.Graph module will pull in everything you need and keeps the setup simple.
Know your target ID
Before running the script, you’ll need to know what object you’re targeting.
For group operations, you’ll need the
- Group Object ID
which you can find in the Microsoft Entra ID portal under the group’s Overview section.
For single-user operations, you can use either:
the User Object ID, or
the UserPrincipalName (UPN), which is usually the user’s email address.
Permissions
Keeping It Least Privilege
When the script connects to Microsoft Graph, it requests four delegated permission scopes:
Connect-MgGraph -Scopes "User.EnableDisableAccount.All", "User.Read.All", "GroupMember.Read.All", "Group.Read.All" -NoWelcome
!tip Each of these scopes is chosen with the Principle of Least Privilege (PoLP) in mind. The idea is simple: the account running the script should only have the permissions required to perform this task, nothing more.
The most important one here is User.EnableDisableAccount.All.
!warning Many examples online default to
User.ReadWrite.All.This permission allows modification of nearly every property on a user object (display name, job title, phone number, password profile, and more), which is far more access than a script like this requires.
User.EnableDisableAccount.All was introduced specifically for scenarios like this and only allows you to toggle the accountEnabled property, meaning it can enable or disable sign-in without granting wider control over user objects.
The remaining three scopes are read-only:
User.Read.Allto retrieve user details such as display name and UPNGroupMember.Read.Allto enumerate the members of a groupGroup.Read.Allto look up the target group using its Object ID
None of these permissions allows any write access. They’re simply there so the script can find the right users and groups before making the enable or disable change.
Role requirements
API permission scopes alone aren’t enough. The account running the script also needs the appropriate Microsoft Entra ID directory role.
The minimum role required to enable or disable user accounts is User Administrator.
One thing to be aware of, though: if any of the accounts you’re targeting to enable/disable hold privileged admin roles such as
Global Administrator,
Exchange Administrator, or
Security Administrator,
The User Administrator role won’t be sufficient. In those situations, you’ll run into an "Insufficient privileges" error.
To manage accounts that hold privileged roles, the account running the script needs the Privileged Authentication Administrator role. This role can manage authentication methods and account status for all users, including those with administrative roles.
For most scenarios, though, such as managing exam accounts or seasonal staff, User Administrator is the correct role to use.
!important Only step up to Privileged Authentication Administrator if you actually need to manage accounts that have elevated privileges.
Locking it down further with Administrative Units
If you want to take the Principle of Least Privilege (PoLP) a step further, Administrative Units (AUs) allow you to scope a role so it applies only to a specific subset of users, rather than to the entire tenant.
By default, if you assign someone the User Administrator role in Microsoft Entra ID, they can manage every user in the directory. That might be fine in a small environment with a single IT team, but in larger organisations such as universities with multiple schools or companies with regional offices, that’s far more access than most admins actually need.
Administrative Units solve this by allowing you to limit the scope.
For example, you could:
Create an Administrative Unit that contains only the accounts an admin needs to manage, such as an "Exam Accounts" AU or a "London Office" AU.
Assign the User Administrator role scoped to that AU, rather than at the tenant level.
Once that’s in place, the admin can enable or disable accounts within that Administrative Unit, but they won’t have any control over users outside of it.
In practice, this means you could give this script to a departmental IT contact and be confident they can only manage the accounts they’re responsible for. Even if they accidentally used the wrong group ID, their permissions would still be limited to the users inside that Administrative Unit.
A few things are worth keeping in mind when working with Administrative Units (AUs):
Licensing: AUs require a Microsoft Entra ID P1 licence for each administrator who is assigned a role scoped to an AU. Users within the AU only need a Free licence.
User membership: Users must be added directly to the AU. If you add a group to an AU, it only brings the group object into scope, not the users inside it. If you want scoped admins to manage those users, they must be added to the AU individually.
Scope of restriction: AUs only restrict management permissions. They don’t stop a scoped admin from reading information about users outside the AU through PowerShell or the Entra admin centre.
!note Think of it as a "can touch vs can see" rule: AUs control what you can touch, not what you can see.
Restricted management AUs: For highly sensitive accounts, you can take it a step further by using restricted management Administrative Units. These can prevent even tenant-level admins, including Global Administrators, from modifying the objects inside unless they explicitly assign themselves a scoped role first. That assignment is logged, making it fully auditable.
In practice, combining:
User.EnableDisableAccount.Allas the API scope,User Administrator as the directory role, and
An Administrative Unit as the role scope
creates a very tight permission boundary.
The person running the script can enable or disable sign-in for a defined set of users and nothing else.
The PowerShell Script
Here’s the full script. Save it as a .ps1 file (for example, Toggle-ExamUsers.ps1) and run it in PowerShell. The interactive menus will guide you through each step and choice.
\(validChoice = \)false
while (-not $validChoice) {
Clear-Host
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host " PAULPOPA.CLOUD EXAM USER ENABLE/DISABLE " -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host " [ 1 ] Press 1 for Enable"
Write-Host " [ 2 ] Press 2 for Disable"
Write-Host " [ 3 ] Press 3 to Exit"
Write-Host ""
$choice = Read-Host "Please make a selection (1, 2, or 3)"
if ($choice -eq '1') {
\(isAccountEnabled = \)true
$actionText = "Enabling"
\(validChoice = \)true
}
elseif ($choice -eq '2') {
\(isAccountEnabled = \)false
$actionText = "Disabling"
\(validChoice = \)true
}
elseif ($choice -eq '3') {
Write-Host "`nExiting script..." -ForegroundColor Yellow
exit
}
else {
Write-Host "`nPlease select 1, 2, or 3." -ForegroundColor Red
Start-Sleep -Seconds 2
}
}
\(validTargetChoice = \)false
while (-not $validTargetChoice) {
Write-Host "`n============================================================" -ForegroundColor Cyan
Write-Host " Are you applying this to a Group or a single User? " -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host " [ 1 ] Press 1 for a Group"
Write-Host " [ 2 ] Press 2 for a Single User"
Write-Host ""
$targetChoice = Read-Host "Please make a selection (1 or 2)"
if ($targetChoice -eq '1') {
$targetType = "Group"
\(validTargetChoice = \)true
}
elseif ($targetChoice -eq '2') {
$targetType = "User"
\(validTargetChoice = \)true
}
else {
Write-Host "`nPlease select 1 or 2." -ForegroundColor Red
Start-Sleep -Seconds 2
}
}
$TargetId = ""
while ([string]::IsNullOrWhiteSpace($TargetId)) {
if ($targetType -eq "Group") {
$TargetId = Read-Host "`nPlease enter the Entra ID Group ID"
}
else {
$TargetId = Read-Host "`nPlease enter the Entra ID User ID (or UserPrincipalName/Email)"
}
if ([string]::IsNullOrWhiteSpace($TargetId)) {
Write-Host "ID cannot be empty. Please try again." -ForegroundColor Red
}
}
Write-Host "`nConnecting to Microsoft Graph..." -ForegroundColor Yellow
Connect-MgGraph -Scopes "User.EnableDisableAccount.All", "User.Read.All", "GroupMember.Read.All", "Group.Read.All" -NoWelcome
if ($targetType -eq "Group") {
Write-Host "Fetching Group details..." -ForegroundColor Yellow
try {
\(Group = Get-MgGroup -GroupId \)TargetId -ErrorAction Stop
\(GroupName = \)Group.DisplayName
}
catch {
Write-Host "`nError: Could not find a group with ID '$TargetId'. Please check the ID and try again." -ForegroundColor Red
Disconnect-MgGraph | Out-Null
Read-Host "Press Enter to exit..."
exit
}
Write-Host "`nStarting bulk \(actionText operation for group members in \)GroupName..." -ForegroundColor Cyan
\(GroupMembers = Get-MgGroupMember -GroupId \)TargetId -All
foreach (\(Member in \)GroupMembers) {
if ($Member.AdditionalProperties["@odata.type"] -eq "#microsoft.graph.user") {
\(userName = \)Member.AdditionalProperties["displayName"]
if ([string]::IsNullOrWhiteSpace($userName)) {
\(userName = \)Member.AdditionalProperties["userPrincipalName"]
}
if ([string]::IsNullOrWhiteSpace($userName)) {
\(userName = \)Member.Id
}
Write-Host "\(actionText User: \)userName... " -NoNewline
Update-MgUser -UserId \(Member.Id -AccountEnabled:\)isAccountEnabled
Write-Host "Done." -ForegroundColor Green
}
}
Write-Host "`nBulk $actionText operation complete." -ForegroundColor Cyan
}
elseif ($targetType -eq "User") {
Write-Host "Fetching User details..." -ForegroundColor Yellow
try {
\(User = Get-MgUser -UserId \)TargetId -ErrorAction Stop
\(userName = \)User.DisplayName
if ([string]::IsNullOrWhiteSpace($userName)) {
\(userName = \)User.UserPrincipalName
}
Write-Host "`nStarting \(actionText operation for user \)userName..." -ForegroundColor Cyan
Write-Host "\(actionText User: \)userName... " -NoNewline
Update-MgUser -UserId \(User.Id -AccountEnabled:\)isAccountEnabled
Write-Host "Done." -ForegroundColor Green
}
catch {
Write-Host "`nError: Could not find a user with ID or Email '$TargetId'. Please check the input and try again." -ForegroundColor Red
Disconnect-MgGraph | Out-Null
Read-Host "Press Enter to exit..."
exit
}
}
Write-Host ""
Write-Host "Disconnecting from Microsoft Graph..." -ForegroundColor Yellow
Disconnect-MgGraph | Out-Null
Write-Host ""
Write-Host "Disconnected safely. Have a great day!" -ForegroundColor Green
Write-Host ""
Read-Host "Press Enter to close this window..."
Wrapping Up
The Microsoft Graph PowerShell SDK is the modern, future-proof way to manage Microsoft Entra ID, now that the older AzureAD and MSOnline modules are retired. The script itself is simple, but the real value comes from getting the permissions right.
Using User.EnableDisableAccount.All instead of User.ReadWrite.All, scoping the User Administrator role to an Administrative Unit, and only stepping up to Privileged Authentication Administrator when necessary gives you a tight, auditable permission boundary that sticks to the Principle of Least Privilege (PoLP).

