Azure AD PowerShell to Microsoft Graph PowerShell

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 #1Microsoft 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.
  • 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.

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:

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.
  • 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:

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:

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:

  1. 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.
  2. 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.

Author