Back to script library
Entra / Microsoft 365 · Users & guests

Report hard deleted user accounts

Example of an Azure Automation runbook to report Entra ID hard-deleted user accounts.

Connect & set up

Run these once per session. All scopes are read-only unless the script makes changes.

Connect-MgGraph -Identity -NoWelcome

Run it

The main script. Copy it, or download the .ps1 and run it from your console.

Connect-MgGraph -Identity -NoWelcome
# Find hard-deleted user accounts from the last 30 days
[array]$DeletedUserRecords = Get-MgAuditLogDirectoryAudit -Filter "ActivityDisplayName eq 'Hard Delete user'" -All -Sort 'ActivityDateTime'
If (!$DeletedUserRecords) {
Write-Host "No hard deleted user records found"; break
}
$Report = [System.Collections.Generic.List[Object]]::new()
ForEach ($Record in $DeletedUserRecords) {
$DataLine = [PSCustomObject][Ordered]@{
TimeStamp = (Get-Date $Record.ActivityDateTime -format 'dd-MMM-yyyy HH:mm:ss')
DeletionInitiatedBy = $Record.InitiatedBy.User.UserPrincipalName
DeletedUser = $Record.TargetResources.UserPrincipalName.Substring(32,($Record.TargetResources.UserPrincipalName.length-32))
}
$Report.Add($DataLine)
}
Write-Output "The following accounts were hard-deleted in the last 30 days"
$Report | Format-Table TimeStamp, DeletionInitiatedBy, DeletedUser -AutoSize
# Define the target SharePoint document library to create the report in. Make sure to update this value to match your tenant
$SiteUri = "https://office365itpros.sharepoint.com/sites/Office365Questions"
$SiteId = $SiteUri.Split('//')[1].split("/")[0] + ":/sites/" + $SiteUri.Split('//')[1].split("/")[2]
$Site = Get-MgSite -SiteId $SiteId
If (!$Site) {
Write-Output ("Unable to connect to site {0} with id {1}" -f $Uri, $SiteId)
Exit
}
[array]$Drives = Get-MgSiteDrive -SiteId $Site.Id
# Locate the default document library
$DocumentsDrive = $Drives | Where-Object {$_.Name -eq "Documents"}
# Generate Excel worksheet - this needs the ImportExcel module - see https://office365itpros.com/2022/05/10/importexcel-powershell/
If (Get-Module ImportExcel) {
# Generate Excel worksheet if we can
Import-Module ImportExcel
# Make sure that we have a unique file name so that we don't overwrite any existing file
$OutputFile = ("HardDeletedUsers-{0}.xlsx" -f (Get-Date -format yyyyMMdd-HHmm))
$Report | Export-Excel -Path $OutputFile -WorksheetName "Hard Deleted User Accounts" -Title "Hard Deleted Accounts" `
-TableName "HardDeletedAccounts"
} Else {
# Generate CSV file if ImportExcel module is not available
$OutputFile = ("HardDeletedUsers-{0}.csv" -f (Get-Date -format yyyyMMdd-HHmm))
$Report | Export-Csv -Path $OutputFile -NoTypeInformation -Encoding UTF8
}
# This works interactively but not in an Azure Automation runbook
# $TargetFile = "root:/General/" + $OutputFile + ":"
# $NewFile = Set-MgDriveItemContent -DriveId $DocumentsDrive.Id -DriveItemId $TargetFile -InFile $File
# So we use the URI method instead
$Uri = ("https://graph.microsoft.com/V1.0/sites/{0}/drive/items/root:/General/{1}:/content" -f $Site.Id, $OutputFile)
$NewFile = Invoke-MgGraphRequest -Uri $Uri -Method PUT -InputFilePath $OutputFile
If ($NewFile) {
Write-Output ("File {0} uploaded to {1} with size {2} MB" -f $NewFile.Name, $DocumentsDrive.Name, ([math]::Round($NewFile.Size/ 1MB, 2)))
}
# Update the document metadata to include a title and description. Note that if the Sites.Selected Graph permission
# is used to gain access to the target site, the Manage rather than Write role is required.
$Uri = ("https://graph.microsoft.com/V1.0/sites/{0}/drive/items/root:/General/{1}:/listItem/fields" -f $Site.Id, $OutputFile)
$Body = @{}
$Body.Add("Title", "Hard Deleted Users Report (Created by Azure Automation)")
$Body.Add("_ExtendedDescription", "This report is generated by an Azure Automation runbook")
# Custom fields added to the target site
# $Body.Add("MoreInfo", "Report listing hard-deleted Entra ID user accounts")
# $Body.Add("NumberofAccounts", $Report.Count)
$ItemUpdate = Invoke-MgGraphRequest -Uri $Uri -Method PATCH -Body $Body
If ($ItemUpdate) {
Write-Output ("Updated document metadata for item {0} with title {1}" -f $OutputFile, $Body.Title)
}
Attribution