Overview:
You might have heard about AzureAD PowerShell module deprecation. So, in this article we will summarize the migration between Azure AD PowerShell to Microsoft Graph PowerShell and will provide you with all the relevant info and links in one place, to get you up and running with the new MS Graph Module.
The best way to learn something new is to watch it in action.
So, in this post I will provide you with a few useful examples of common scenarios where you can use MSGraph:
- A script for disabling inactive users.
- A script for deleting disabled users.
- Running the scripts with Azure Automation runbooks, using a managed identity, and finally, exporting all the data to a CSV file in a Blob storage.
What is the MSGraph module?
Microsoft Graph PowerShell is the PowerShell module to use for interacting with Azure AD and other Microsoft services (SharePoint, Exchange, and Outlook) and it replaces the legacy AzureAD PowerShell.
Microsoft Graph PowerShell provides you with a tool for managing resources in Azure AD (among other things), in a wider range of capabilities:
Rich and advanced queries, a more flexible approach to querying and manipulating data than AzureAD PowerShell, support modern authentication, cross platforms, etc.
Example #1 – Microsoft Graph PowerShell using Azure Automation account runbooks with Managed identity:
What is a Managed Identity?
To allow interaction between resources, we need to have a type of authentication.
Azure Managed Identity is a feature of Azure Active Directory (AAD) that allows Azure resources to authenticate to other Azure resources without having to manage their credentials.
Azure automatically manages the identity of the resource and the authentication process without the need for credential management.
Managed Identity types:
- System-assigned
-
Shared life cycle with the Azure resource that the managed identity is created with.
When the parent resource is deleted, the managed identity is deleted as well. - Associated to only one single resource.
-
Shared life cycle with the Azure resource that the managed identity is created with.
- User-assigned
- Created as a stand-alone resource and assign to one or more Azure resources.
- User-assigned Managed Identity has its independent life cycle and must be explicitly deleted.
References:
- For a better understanding about Managed Identity – Managed identities for Azure resources – Microsoft Entra | Microsoft Learn
- View a list of Azure services that support Managed Identity – Azure Services with managed identities support – Azure AD – Microsoft Entra | Microsoft Learn
- Managed Identity differences – Managed identity types
How to enable the Managed Identity access permissions to Azure AD data?
When we assign Azure Managed Identity, it automatically creates an Enterprise Application object under Azure AD.
The Enterprise Application allows us to grant API permissions to Azure AD to manage user data as we will see in the following example:
The entire solution architecture:

First step: Configure the infrastructure:
Create an Azure Automation Account and assign System Managed Identity
- Azure portal 🡪 Azure Automation Account 🡪 Create
- Make sure “System assigned” is on and copy the Object ID for later (for granting permission to the enterprise
application)

Assign MS.Graph permissions to the Enterprise Application Managed Identity
- Azure portal 🡪 Azure AD 🡪 Enterprise applications 🡪 All applications 🡪 change the “Application Type == Managed Identity”
You will see the Azure Automation account managed identity you created in the previous step.

- MS.Graph permissions can only be assigned via PowerShell (as of writing of this post)
The following PowerShell script will add the requested Microsoft Graph permissions to a Managed Identity:

Please note that executing any script or code provided on this blog is done at your own responsibility. It is important to carefully review and testing any script or code prior to execution, and to ensure that it is compatible with your system and meets your specific needs.
# Your tenant id (Azure Portal 🡪 Azure Active Directory 🡪 Overview)
$TenantID=""
# Microsoft Graph App ID (DON'T CHANGE - Microsoft Graph ID is the same in all tenants)
$GraphAppId = "00000003-0000-0000-c000-000000000000"
#Specify the Managed Identity ID. (Azure Portal 🡪 Azure resource instance (in our example – Automation Account) 🡪 Managed Identity)
$ManagedIdentityID =""
# Add the permission you need for the operation (the below permissions are needed in our scenario)
$Permissions = "User.Read.All", "AuditLog.Read.All", "User.ReadWrite.All", "DelegatedPermissionGrant.ReadWrite.All", "Directory.ReadWrite.All"
# Install the module if it’s not already been installed
Install-Module AzureAD
Connect-AzureAD -TenantId $TenantID
#Find the application in AzureAD object
$GraphApp = Get-AzureADServicePrincipal -Filter "AppId eq '$GraphAppId'"
#Assign all the permissions to the Managed Identity
foreach ($permission in $permissions)
{
$role = $GraphApp.AppRoles | Where-Object {$_.Value -eq $permission}
New-AzureADServiceAppRoleAssignment -ObjectId $ManagedIdentityID -ResourceId $GraphApp.ObjectId -Id
$role.Id -PrincipalId $ManagedIdentityID
}
#ObjectId = the Managed Identity object
#ResourceId = "define by" - the Microsoft Graph
#Id = the role ID
#PrincipalId = the object that will receive the permission - the Managed Identity
After executing the script:
Enterprise Application 🡪 Permissions
The requested permissions are assigned to the Managed Identity:
Create an Azure Storage account and grant permission to the managed identity
- Azure portal 🡪 Azure Storage Account 🡪 Create
- Assign “Storage Blob Data Contributor” role to the managed identity
Storage Blob Contributor role is needed because the runbook will create a container and CSV files with the users data


Second step: Install Azure MSGraph PowerShell Modules:
To use Azure MSGraph PowerShell, we need to import the relevant modules (based on the actions we are going to do).
For our scripts, we need to import those modules:
- Microsoft.Graph.Authentication
- Microsoft.Graph.Reports
- Microsoft.Graph.Users
- Microsoft.Graph.Users.Actions
References:
- For all Microsoft.Graph modules list and actions – Microsoft Graph PowerShell documentation | Microsoft Learn
(Under Reference) - John Savill – Managing with Microsoft Graph (and PowerShell) – YouTube
Azure Portal 🡪 Automation Account 🡪 Modules 🡪 Add a module.

Choose “Browse from gallery” 🡪 browse from gallery 🡪 type the name of the module 🡪 select.

You should see all modules in “Available” status after they have been imported successfully:

Third step: Create a PowerShell Runbook for the “Disabled Users” script:
- The bellow script will use Azure MSGraph to manage Azure AD users.
- The script will receive all “Enabled” users who haven’t logged in for 90 days and disable them.
- The sign-in activity report is available in all editions of Azure AD. If you have an Azure Active Directory P1 or P2 license, you can access the sign-in activity report through the Microsoft Graph API.
Sign-in logs in Azure Active Directory – Microsoft Entra | Microsoft Learn
- The sign-in activity report is available in all editions of Azure AD. If you have an Azure Active Directory P1 or P2 license, you can access the sign-in activity report through the Microsoft Graph API.
- If the user has multiple logins, it will refer to the most recent one.
- All the data regarding the user will be exported to a CSV file inside a Blob container, which will create automatically.
- The script will disable all relevant users.
- The runbook will be attached to a schedule to run regularly.
Azure Portal 🡪 Automation Account 🡪 Runbooks 🡪 Create a runbook.


In the “Edit” window, you can insert the below script:
The following PowerShell script will disable AD users using Microsoft Graph:
#Connect-AzAccount using Azure Automation Managed Identity
Connect-AzAccount -identity
#Sign in to MgGraph
function Get-AzToken
{
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[String]
$ResourceUri,
[Switch]$AsHeader
)
$Context =
[Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
$Token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, $ResourceUri).AccessToken
if ($AsHeader) {
return @{Headers = @{Authorization = "Bearer $Token" } }
}
return $Token
}
$Token = Get-AzToken -ResourceUri 'https://graph.microsoft.com/'
Connect-MgGraph -AccessToken $Token
#Insert values of your subscription id, storage account resource group and storage account name in order to automatically create the Blob container
$subscriptionId = " "
$storageAccountRG = " "
$storageAccountName = " "
$todaydate = Get-Date -Format dd-MM-yy
$dateday = Get-Date -DisplayHint Date
$storageContainerName = "disableusers-$todaydate"
# Select Azure Subscription
Select-AzSubscription -SubscriptionId $SubscriptionId
# Get Storage Account Key
$storageAccountKey = (Get-AzStorageAccountKey -ResourceGroupName $storageAccountRG -AccountName $storageAccountName).Value[0]
# Set AzureStorageContext and create new container per running date
$context = New-AzureStorageContext -StorageAccountName $storageAccountName -StorageAccountKey $storageAccountKey
New-AzureStorageContainer -Name $storageContainerName -Context $context
#Get the date from 90 days ago
$SetDate = (Get-Date).AddDays(-90);
$SetDate = Get-Date($SetDate) -format yyyy-MM-dd
#Get all Azur AD users in 'Enable' status. (Without the '-All' it will represent only the first 100 users)
$AllUsers= Get-MgUser -Filter "AccountEnabled eq true" -All
#Get Sign in logs using Get-MgAuditLogSignIn
$AllSiginLogs = Get-MgAuditLogSignIn -All
$AllDisableUsers = @()
foreach($user in $AllUsers)
{
Select-MgProfile beta
$LoginRecord = Get-MgUser -UserId $user.Id -Property signinactivity | Select-Object -ExpandProperty SignInActivity | Sort-Object CreatedDateTime -Descending
if($LoginRecord.Count -gt 0)
{
$lastLogin = $LoginRecord.LastSignInDateTime
$log = $AllSiginLogs | Where-Object{ $_.Id -eq $LoginRecord.LastSignInRequestId }
if($lastLogin -lt $SetDate)
{
Write-Output "Last logon time, user can be disable : " $lastLogin
Write-Output "Last logon time, user can be disable : " $user.DisplayName
$UserObj = [pscustomobject]@{
ID = $user.Id
Name = $user.DisplayName
UPN = $user.UserPrincipalName
LastLogin = $lastLogin
appDisplayName = $log.appDisplayName
}
$AllDisableUsers += $UserObj
}
}
else
{
$lastLogin = 'no login record'
Write-Output "Last logon time, user can be disable : " $user.DisplayName
$UserObj = [pscustomobject]@{
ID = $user.Id
Name = $user.DisplayName
UPN = $user.UserPrincipalName
LastLogin = "no login record"
appDisplayName = ""
ipAddress = ""
clientAppUsed = ""
}
$AllDisableUsers += $UserObj
}
$LogFull = "DisableUsers.csv"
}
$AllDisableUsers | Select-Object ID, @{N="Display Name"; E={$_.Name}}, UPN, @{N="Last Login"; E={$_.LastLogin}}, @{N="App Display Name"; E={$_.appDisplayName}}, @{N="IP Address"; E={$_.ipAddress}}, @{N="Client App Used"; E={$_.clientAppUsed}} | Export-Csv -Path $LogFull -Append -NoTypeInformation
Set-AzureStorageBlobContent -Context $context -Container $storageContainerName -File $LogFull
#Disable all users
foreach ($du in $AllDisableUsers)
{
Update-MgUser -UserId $du.UPN -AccountEnabled:$false
}
References:
- Get-MgAuditLogSignIn options: Get-MgAuditLogSignIn (Microsoft.Graph.Reports) | Microsoft Learn
The output should look like:

In the Azure Storage Account, you should see the current container with the CSV file.

Fourth step: Create a PowerShell Runbook for the “Deleted Users” script:
- The bellow script will use Azure MSGraph to manage Azure AD users.
- The script will receive all “Disabled” users for the last 90 days and delete them.
- If the user has been disabled multiple times, it will refer the last time it has been disabled.
- All the data regarding the user that needs to be deleted, we be exported to a CSV file inside a Blob container, which will create automatically.
- The script will delete all relevant users.
- The runbook will be attached to a schedule to run regularly.
Create new PowerShell Runbook as the step before and past the following script:
The following PowerShell script will delete AD users using Microsoft Graph:
#Connect-AzAccount using Azure Automation Managed Identity
Connect-AzAccount -identity
#Sign in to MgGraph
function Get-AzToken
{
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[String]
$ResourceUri,
[Switch]$AsHeader
)
$Context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
$Token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, $ResourceUri).AccessToken
if ($AsHeader) {
return @{Headers = @{Authorization = "Bearer $Token" } }
}
return $Token
}
$Token = Get-AzToken -ResourceUri 'https://graph.microsoft.com/'
Connect-MgGraph -AccessToken $Token
#Insert values of your subscription id, storage account resource group and storage account name in order to automatically create the Blob container
$subscriptionId = " "
$storageAccountRG = " "
$storageAccountName = " "
$todaydate = Get-Date -Format dd-MM-yy
$dateday = Get-Date -DisplayHint Date
$storageContainerName = "deleteusers-$todaydate"
# Select Azure Subscription
Select-AzSubscription -SubscriptionId $SubscriptionId
# Get Storage Account Key
$storageAccountKey = (Get-AzStorageAccountKey -ResourceGroupName $storageAccountRG -AccountName $storageAccountName).Value[0]
# Set AzureStorageContext and create new container per running date
$context = New-AzureStorageContext -StorageAccountName $storageAccountName -StorageAccountKey $storageAccountKey
New-AzureStorageContainer -Name $storageContainerName -Context $context
#Get the date from 90 days ago
$SetDate = (Get-Date).AddDays(-90);
$SetDate = Get-Date($SetDate) -format yyyy-MM-dd
#Get all Azur AD users in 'Disable' status. (Without the '-All' it will represent only the first 100 users)
$AllUsers= Get-MgUser -Filter "AccountEnabled eq false" -All
$AllDeletedUsers = @()
foreach($user in $AllUsers)
{
$userspn = $user.UserPrincipalName
$AllDisabledLogs = Get-MgAuditLogDirectoryAudit -Filter "ActivityDisplayName eq 'Disable account' and TargetResources/any(t:t/UserPrincipalName eq '$userspn')" -All | Sort-Object ActivityDateTime -Descending
if($AllDisabledLogs.Count -gt 1)
{
$lastDisabled = $AllDisabledLogs[0].ActivityDateTime
if($AllDisabledLogs[0].InitiatedBy.User.DisplayName -eq $null)
{
$disabledby = $AllDisabledLogs[0].InitiatedBy.App.DisplayName
}
else
{
$disabledby = $AllDisabledLogs[0].InitiatedBy.User.DisplayName
}
if($lastDisabled -lt $SetDate)
{
Write-Output "user can be deleted, last disabled time : " $lastdisabled
$UserObj = [pscustomobject]@{
ID = $user.Id
Name = $user.DisplayName
UPN = $user.UserPrincipalName
LastDisabled = $lastDisabled
disabledby = $disabledby
deleted = "yes"
}
$AllDeletedUsers += $UserObj
}
else
{
Write-Output "user cannot be deleted, last disabled time : " $lastdisabled
$UserObj = [pscustomobject]@{
ID = $user.Id
Name = $user.DisplayName
UPN = $user.UserPrincipalName
LastDisabled = $lastdisabled
disabledby = $disabledby
deleted = "no"
}
$AllDeletedUsers += $UserObj
}
}
else
{
if($AllDisabledLogs.InitiatedBy.User.DisplayName -eq $null)
{
$disabledby = $AllDisabledLogs.InitiatedBy.App.DisplayName
}
else
{
$disabledby = $AllDisabledLogs.InitiatedBy.User.DisplayName
}
if($AllDisabledLogs.ActivityDateTime -lt $SetDate)
{
Write-Output "user can be deleted, last disabled time : "
$AllDisabledLogs.ActivityDateTime
$UserObj = [pscustomobject]@{
ID = $user.Id
Name = $user.DisplayName
UPN = $user.UserPrincipalName
LastDisabled = $AllDisabledLogs.ActivityDateTime
disabledby = $disabledby
deleted = "yes"
}
$AllDeletedUsers += $UserObj
}
else
{
Write-Output "user cannot be deleted, last disabled time : "
$AllDisabledLogs.ActivityDateTime
$UserObj = [pscustomobject]@{
ID = $user.Id
Name = $user.DisplayName
UPN = $user.UserPrincipalName
LastDisabled = $AllDisabledLogs.ActivityDateTime
disabledby = $disabledby
deleted = "no"
}
$AllDeletedUsers += $UserObj
}
}
$LogFull = "DeletedUsers.csv"
}
$AllDeletedUsers
$AllDeletedUsers | Select-Object ID, @{N="Display Name"; E={$_.Name}}, UPN, @{N="Last Disabled"; E={$_.LastDisabled}}, @{N="Disabled by"; E={$_.disabledby}}, @{N="To be deleted"; E={$_.deleted}} | Export-Csv -Path $LogFull -Append -NoTypeInformation
Set-AzureStorageBlobContent -Context $context -Container $storageContainerName -File $LogFull
#Delete the user after 90 days that has been disabled
foreach ($du in $AllDeletedUsers)
{
if($du.deleted -eq "yes")
{
Remove-MgUser -UserId $du.ID
}
}
References:
- Information about filtering for Get-MgAuditLogDirectoryAudit: List directoryAudits – Microsoft Graph v1.0 | Microsoft Learn
Use Managed Identity to manage “Global Administrator” users:
Both scripts can manage “un-powered” users.
If you want to manage (disable/delete) “Global Administrator” users, you need to do the following steps:
- Create a new Azure group:
- Make sure to mark the “Azure AD roles can be assigned to the group”.
- Assign the group “Global Administrator” role.
- Assign the Azure group to the Managed Identity:


Azure portal 🡪 Azure AD 🡪 Enterprise applications 🡪 All applications 🡪 change the “Application Type == Managed Identity”
You will see the Azure Automation account managed identity.

Click on the “Self-Service” tab and select the Azure group you created the step before:

Under “Users and groups” tab you will see the assigning group.

To summarize, in this post I discussed the migration from Azure AD PowerShell to Microsoft Graph PowerShell, highlighting the benefits and reasons why the transformation is important. The post provides links and examples of common scenarios, including how to assign permissions to a managed identity and manage users’ status (disabling and deleting users) using Microsoft Graph PowerShell, also provides helpful tips and resources for getting started to ensure a smooth and successful transition.
You must log in to post a comment.