Delete and Restore Azure AD User Accounts with the Microsoft Graph PowerShell SDK

Service Principal is Latest Critical Azure AD Object to Support Recovery

According to message center notification MC344406 (18 March), in early April Microsoft plans to roll-out the capability of recovering deleted service principal objects. Service principals are critical parts of registered Azure AD apps, such as the apps used to execute Microsoft Graph API queries with PowerShell. They’re also used in Azure Automation accounts, the Microsoft Graph PowerShell SDK, and managed identities. In all cases, the service principals hold the permissions needed for an app or account to operate. The worldwide roll-out to all tenants should complete by late May.

When the capability is available, any time an administrator deletes a service principal (for instance, because a registered app is no longer needed) using the Azure AD admin center, PowerShell (using Remove-AzureADServicePrincipal), or the Microsoft Graph API, Azure AD will place the service principal into a soft-deleted state. This already happens today for user, group, device, and application objects.

Deleted Azure AD objects stay in the deleted items container for 30 days. When the retention period elapses (extending to maybe a few days afterwards), Azure AD proceeds to permanently delete the object.

During the retention period, administrators can restore an object, which makes it easy to recover if someone deletes an important item by accident. For now, the list deleted items API doesn’t support service principals, but it will after the roll-out. Figure 1 shows user objects in the deleted items container as viewed through the Graph Explorer.

Viewing deleted Azure AD user accounts via the Graph Explorer

Delete Azure AD user account
Figure 1: Viewing deleted Azure AD user accounts via the Graph Explorer

Using Old Azure AD Cmdlets

MC344406 features two cmdlets from the Azure AD Preview module:

In some respects, it’s odd that they use cmdlets based on the Azure AD Graph API because Microsoft has scheduled the Azure AD modules for retirement at the end of 2022.

Of course, apart from the licensing management cmdlets, the rest of the Azure AD cmdlets will continue to work after retirement, which makes it perfectly acceptable to specify the cmdlets now, especially if replacements in the Microsoft Graph PowerShell SDK are unavailable.

Using Microsoft Graph PowerShell SDK Cmdlets with Deleted Azure AD User Accounts

The Microsoft Graph PowerShell SDK can be difficult to navigate. It’s composed of 38 separate sub-modules. Although cmdlets are gathered logically, it can still be hard to find the right cmdlet to do a job. As you’d expect, the current version (1.9.3) doesn’t appear to include cmdlets to handle soft-deleted service principal objects. For now, we can see how to perform common administrative actions with user accounts as a guide to what should be available for service principals.

With that in mind, here are the steps to soft-delete user accounts, list the accounts in the deleted items container, and hard-delete (permanently remove) an account.

Soft-Delete an Azure AD User Account

To soft-delete an Azure AD account, run the Remove-MgUser and pass the object identifier or user principal name of the account to delete. The cmdlet does not prompt for a confirmation and deletes the account immediately:

Remove-MgUser -UserId

List Soft-Deleted Azure AD User Accounts

During the 30-day retention period in the deleted items container, you can recover the account from the Azure AD admin center or by running the Restore-MgUser cmdlet. Before we can run Restore-MgUser, we need to know the object identifiers of the objects in the deleted items container. This code:

  • Uses the Get-MgDirectoryDeletedItem cmdlet to fetch the list of deleted user accounts (instead of passing an object identifier, we use the special microsoft.graph.user term to tell Azure AD that we want a list of user accounts). The Property parameter can be ‘*’ to return all properties of the deleted objects, but in this case, I’ve chosen to limit the set of properties to those that I want to use.
  • Loops through the data returned by Azure AD to extract the properties we want to use. The different behaviour of the Azure AD cmdlets and the Microsoft Graph PowerShell SDK cmdlets is an example of why tenants need to plan the upgrade and testing of scripts which use the Azure AD cmdlets.
  • Lists the soft-deleted user accounts.
$DeletedItems = Get-MgDirectoryDeletedItem -DirectoryObjectId microsoft.graph.user -Property 'Id','displayname', 'deletedDateTime', 'userType'
[int]$n = $DeletedItems.AdditionalProperties['value'].count
If ($n -eq 0) { 
   Write-Host "No deleted accounts found - exiting"; break }

$n = $n-1

$Report = [System.Collections.Generic.List[Object]]::new()

For ($i = 0; $i -le $n; $i++) {
    $Command = "$" + "DeletedItems.AdditionalProperties" + "['value'][$i]"
    $Item = Invoke-Expression $Command
    $DeletedDate = Get-Date($Item['deletedDateTime'])
    $ReportLine = [PSCustomObject][Ordered]@{ 
           UserId   = $Item['id']
           Name     = $Item['displayName']
           Deleted  = $DeletedDate
           "Days Since Deletion" = (New-TimeSpan $DeletedDate).Days
           Type     = $Item['userType'] }
$Report | Sort {$_.Deleted -as [datetime]} | Format-Table UserId, Name, Deleted, "Days Since Deletion", Type -AutoSize 

UserId                               Name                      Deleted             Days Since Deletion Type
------                               ----                      -------             ------------------- ----
92cef396-1bd3-4296-b06f-786e2ee09077 The Maestro of Office 365 19/02/2022 17:36:44                  31 Guest
c6133be4-71d4-47c4-b109-e37c0c93f8d3 Oisin Johnston            26/02/2022 18:13:26                  24 Member
2e9f1189-d2d9-4301-be57-2d66f3df6bb1 Jessica Chen (Marketing)  04/03/2022 11:52:48                  18 Member
8cd64635-bce6-4af0-8e64-3bebe354e9a4 Alex Redmond              05/03/2022 17:36:45                  17 Member
0f16501c-8302-468a-99a6-78c22b0903d2 Jennifer Caroline         18/03/2022 21:33:13                   3 Member
3a6116ab-0116-490e-bd60-7e0cd9f36c9d Sue Ricketts (Operations) 20/03/2022 19:53:29                   2 Member
4a25ccf0-17df-42cf-beeb-4fd449531b47 Stephen Rice              22/03/2022 19:30:06                   0 Guest

To restore a soft-deleted Azure AD account, run the Restore-MgDirectoryDeletedItem cmdlet and pass the account’s identifier. After restoring the account, remember to assign licenses to allow the account to access Microsoft 365 services.

Restore-MgDirectoryDeletedItem -DirectoryObjectId 3a6116ab-0116-490e-bd60-7e0cd9f36c9d

Remove Soft-Deleted Azure AD User Account

To remove a soft-deleted directory object, run the Remove-MgDirectoryDeletedItem cmdlet and pass the object identifier. Like Remove-MgUser, the cmdlet doesn’t ask for confirmation and permanent deletion happens immediately.

Remove-MgDirectoryDeletedItem -DirectoryObjectId f9d30b84-ad5f-4151-98f0-a55dafe30829

Time of Transition

We’re in a time of transition now as Microsoft does its best to retire the Azure AD modules and build the capabilities (and hopefully the documentation) of the Microsoft Graph PowerShell SDK. In the intervening period, any time you see an example using Azure AD cmdlets, try to convert it to use the SDK. It’s a great way to learn.

Keep up to date with developments like the Microsoft Graph PowerShell SDK by subscribing to the Office 365 for IT Pros eBook. Our monthly updates make sure that our subscribers understand the most important changes happening across Office 365.

10 Replies to “Delete and Restore Azure AD User Accounts with the Microsoft Graph PowerShell SDK”

  1. Hello, I tried the script using the Graph Powershell SDK but it doesn’t return the deleted groups and users. The special microsoft.graph.user / terms don’t seem to work for me. The old Azure AD cmdlets, on the contrary, return all the soft-deleted groups/users. Am I doing something wrong? I have all the required scopes so I don’t think it’s a permission problem.
    Any suggestions are welcome!

      1. Hello,
        I have done some searching and I found this:
        Apparently it’s a known issue and I was able to get the soft-deleted users/groups using “thuld” and “vandreytrindade” suggestions.
        I am posting the link with the temporary workaround here for other users who might have the same issue.
        I confirm that also “Invoke-GraphRequest” works ok.
        Many thanks for your time.

  2. My apologies, your script works fine and you are also using the “AdditionalProperties[‘value’]” property. I was running only the
    “Get-MgDirectoryDeletedItem -DirectoryObjectId microsoft.graph.user -Property ‘*'” expecting to see a list of the soft-deleted users/groups but always got no output.
    Sorry again for the confusion!

  3. Hello,
    I would like to add that like many other Graph SDK command, the “Get-MgDirectoryDeletedItem” returns only 100 results and when you use the “-DirectoryObjectId” parameter you don’t have the option to get all the results. We have recently deleted 250 users and I get only 100 when running the report.
    Is there a way to use your script without having the “100 results” limitation? Or should I still use the old cmdlets from the MSOnline/AzureAD modules?
    Many thanks in advance for your time!

    1. You’re hitting a thing called pagination, where Graph API requests return a limited amount of data to the initial request to prevent requests asking for thousands of items without good reason.

      The Get-MgDirectoryDeletedItem cmdlet supports an All parameter to return all matching items. I have played with it and can’t make it work, but I will keep on trying. In the interim, you can always revert to the API call and do:

      $Uri = “”
      [array]Data = Invoke-MgGraphRequest -Method Get -Uri $Uri

      And check the odata.nextlink returned. If one exists, you know more data is available and you can retrieve it (see for an explanation).

      1. Microsoft acknowledges a problem in this area. They’re working to fix it. In the interim, use the Graph method I gave you and all will be well.

  4. Thank you for your input, I also tried to use the “All” parameter but to no avail. And when I try the “Search” and “Filter” parameters, I got the error message saying

    “Request with $search query parameter only works through MSGraph with a special request header: ‘ConsistencyLevel: eventual'”.

    When you use the “Get-MgUser” cmdlet you have the parameter “-ConsistencyLevel eventual” but you don’t have that for the “Get-MgDirectoryDeletedItem” cmdlet, which is a bit frustrating (
    I will try the API call and go through all the odata.nextlink.
    Many thanks again

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.