If you want to restore deleted Azure AD objects via Graph, there’s a cmdlet for it. You might find references to Restore-MgUser
and such, but those don’t work (and probably never did) because of which the cmdlets were removed.
1 2 |
Import-Module Microsoft.Graph.Identity.DirectoryManagement Restore-MgDirectoryDeletedItem -DirectoryObjectId $directoryObjectId |
It has a Get- variant which you’d think can be used to search for deleted objects, find their Id, and use that with the cmdlet above to do a restore. I couldn’t get it working though:
1 2 |
> Get-MgDirectoryDeletedItem -All Get-MgDirectoryDeletedItem_List: Searches against this resource are not supported. Only specific instances can be queried. |
Looks like it can just get an item by its Id. Pointless.
So I resorted to the API itself. You need to do a GET against the Graph endpoint /directory/deletedItems/microsoft.graph.user
. Good thing is you can even filter against specific properties.
At a basic level you do the following:
1 |
Invoke-MgGraphRequest -Uri 'https://graph.microsoft.com/v1.0/directory/deletedItems/microsoft.graph.user' |
In the output all the Ids are in the value
property. Additional values, if any, can be got by following the link in the @odata.nextLink
property.
If you want to filter, that’s easy too.
1 |
Invoke-MgGraphRequest -Uri 'https://graph.microsoft.com/v1.0/directory/deletedItems/microsoft.graph.user?$filter=mail eq ''abc@mydomain.com''' -Headers @{ ConsistencyLevel = "eventual" } |
Notice the double single quotes around the entry I am searching for. This is important. It needs to be single quotes, but since they are already within single quotes I need to escape them. And I need the whole Url to be in single quotes because of the $filter
– if I use double quotes, PowerShell treats it like a variable.
If searching by UPN, use endsWith
as the UPN gets changed when an account is deleted.
1 |
Invoke-MgGraphRequest -Uri 'https://graph.microsoft.com/v1.0/directory/deletedItems/microsoft.graph.user?$filter=endsWith(userPrincipalName,''abc@mydomain.com'')' -Headers @{ ConsistencyLevel = "eventual" } |
For both of these filtered searches the ConsistencyLevel header matters, and should be set to “eventual”.
On a side note, it is also possible to use Graph API to change the immutable ID of an object. This is possible due to an Azure AD bug and will stop working at some point… Essentially, if you delete an object from on-prem, it will move the Azure AD object to the Deleted Items container. You can then restore it as above, change the UPN to a cloud one, set a new immutable ID (say of an account from a different forest with which you want to associate this Azure AD account), and set the UPN to the new one.
Kind of like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
$OldUPN = "something" $NewUPN = "something else" $cloudTenant = "something.onmicrosoft.com" $UserName = ($OldUPN -split '@')[0] $tempUPN = "${userName}@${cloudTenant}" # I do the Url like this because $OldUPN is a variable and I want PowerShell to treat it like that. Also, I am not sure if $count is required, but I add it as a just in case coz some searches require it. No harm adding. $filterUrl = 'https://graph.microsoft.com/v1.0/directory/deletedItems/microsoft.graph.user?$filter=endsWith(userPrincipalName,' $filterUrl = $filterUrl + "'$OldUPN')" + '&$count=true' $deletedObj = Invoke-MgGraphRequest -Uri $filterUrl -Headers @{ ConsistencyLevel = "eventual" } if ($deletedObj.'@odata.count' -eq 0) { # Output error or do nothing } else { $userId = $deletedObj.Value.id Restore-MgDirectoryDeletedItem -DirectoryObjectId $userid | Out-Null Update-MgUser -UserId $userId -UserPrincipalName $tempUPN Update-MgUser -UserId $userId -OnPremisesImmutableId $newImmutableId Update-MgUser -UserId $userId -UserPrincipalName $NewUPN $userObj = Get-MgUser -UserId $NewUPN -Property OnPremisesImmutableId if ($userObj.OnPremisesImmutableId -eq $newImmutableId) { "All good!" } } |
That’s all!