Entra / Microsoft 365 · Exchange Online
Report recoverable items
Use the Microsoft Graph PowerShell SDK to report details of items in the Exchange Online Recoverable Items structure.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-ExchangeOnline -SkipLoadingCmdletHelp
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([string] $TenantId = "",[string] $AppId = "1d58578ac-7cb4-4b5a-a296-f19218a03f11",[int] $LookbackDays = 365,[string] $StartDate = (Get-Date).AddDays(-$LookbackDays),[string] $EndDate = (Get-Date))function FormatFileSize {# Format File Size nicelyparam ([parameter(Mandatory = $true)]$InFileSize)If ($InFileSize -lt 1KB) { # Format the size of a document$FileSize = $InFileSize.ToString() + " B" }ElseIf ($InFileSize -lt 1MB) {$FileSize = $InFileSize / 1KB$FileSize = ("{0:n2}" -f $FileSize) + " KB"}Elseif ($InFileSize -lt 1GB) {$FileSize = $InFileSize / 1MB$FileSize = ("{0:n2}" -f $FileSize) + " MB" }Elseif ($InFileSize -ge 1GB) {$FileSize = $InFileSize / 1GB$FileSize = ("{0:n2}" -f $FileSize) + " GB" }Return $FileSize}# Connect to Exchange Online, if we're not already connected$Modules = Get-Module | Select-Object -ExpandProperty NameIf ("ExchangeOnlineManagement" -notin $Modules) {Write-Host "Connecting to Exchange Online..."Connect-ExchangeOnline -SkipLoadingCmdletHelp}# Values to use with Connect-MgGraph. These will be different for your tenant. The app identifier is for# an app registered in Entra ID. The thumbprint is for a certificate loaded into the app.$Thumbprint = 'F79286DB88C21491110109A0222348FACF694CBD'# Define the search period. In this example, we look back 365 days.[datetime]$StartDate = (Get-Date).AddDays(-$LookbackDays)[string]$StartDate = Get-Date $StartDate -Format "yyyy-MM-ddTHH:mm:ssZ"[string]$EndDate = Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ"# Connect to the Graph with an Entra ID app that has the Mail.Read permission.Connect-MgGraph -AppId $AppId -TenantId $TenantId -CertificateThumbprint $Thumbprint -NoWelcomeWrite-Host "Scanning for mailboxes..."[array]$NoReportFolders = @("Audits", "Calendar Logging")[array]$Mbx = Get-ExoMailbox -RecipientTypeDetails UserMailbox -ResultSize Unlimited | Sort-Object DisplayNameIf (!($Mbx)) {Write-Host "No mailboxes found - exiting!"; break}Write-Host ("Processing {0} mailboxes..." -f $Mbx.Count)$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($M in $Mbx) {Write-Host ("Processing mailbox {0}" -f $M.UserPrincipalName) -ForegroundColor Yellow# Graph request# $Uri = ("https://graph.microsoft.com/v1.0/users/{0}/MailFolders/RecoverableItemsRoot/childfolders" -f $M.ExternalDirectoryObjectId)Try {$RecoverableItemsRoot = (Get-MgUserMailFolder -Userid $M.ExternalDirectoryObjectId -MailFolderId 'RecoverableItemsRoot').id[array]$Folders = Get-MgUserMailFolderChildFolder -MailFolderId $RecoverableItemsRoot -UserId $M.ExternalDirectoryObjectId}Catch {Write-Host ("Error {0} when processing mailbox {1}" -f $_.Exception.Message, $M.UserPrincipalName)Continue}Write-Host ("Found {0} folders in Recoverable Items" -f $Folders.Count)ForEach ($Folder in $Folders){# Ignore the folders used for mailbox auditing and calendar loggingIf ($Folder.displayName -in $NoReportFolders) {# Write-Host "Ignored folder" $Folder.displayNameContinue}If ($Folder.TotalItemCount -gt 0) {Write-Host ("Processing folder {0} in mailbox" -f $Folder.displayName, $M.displayName)# Graph API request# $Uri = ("https://graph.microsoft.com/v1.0/users/{0}/mailfolders/{1}/Messages/?`$select=sender,createdDateTime,subject&`$expand=singleValueExtendedProperties(`$filter=Id%20eq%20'LONG%200x0E08')" -f $M.ExternalDirectoryObjectId, $Folder.id)# Consider adding BodyPreview to the set of properties for eDiscovery use[array]$Items = Get-MgUserMailFolderMessage -UserId $M.ExternalDirectoryObjectId -MailFolderId $Folder.id -All `-Property sender,createdDateTime,subject -PageSize 999 `-ExpandProperty "singleValueExtendedProperties(`$filter=Id eq 'LONG 0x0E08')" `-Filter "(ReceivedDateTime ge $StartDate and ReceivedDateTime le $EndDate)"# If some items are returned, report themWrite-Host ("Found {0} items in folder {1}" -f $Items.Count, $Folder.displayName)ForEach ($Item in $Items) {[long]$ItemFileSize = $Item.singleValueExtendedProperties.value$ReportLine = [PSCustomObject][Ordered]@{Mailbox = $M.UserPrincipalNameFolder = $Folder.displayNameSubject = $Item.SubjectSender = $Item.sender.emailAddress.addressCreated = Get-Date($Item.createdDateTime).ToLocalTime()Size = FormatFileSize -InFileSize $ItemFileSize}$Report.Add($ReportLine)}}}}Write-Host ("Details of {0} items from Recoverable Items folders reported from {1} mailboxes" -f $Report.count, $Mbx.count)$Report | Out-GridView -Title ("Items found in Recoverable Items folder from {0}" -f $StartDate)$Report | Export-CSV -Encoding utf8 c:\temp\RecoverableItemsFiles.csvWrite-Host "Output CSV file available in c:\temp\RecoverableItemsFiles.csv"
Parameters
ParameterDefaultNotes
-TenantId""Microsoft Entra tenant ID for app-only Graph authentication.-AppId""Application (client) ID for the app registration used to connect.-LookbackDays365Number of days back to filter recoverable items by received date.-StartDate(Get-Date).AddDays(-10)Start of the reporting window.-EndDate(Get-Date)End of the reporting window.Attribution
Author
Office365itpros