Back to script library
Entra / Microsoft 365 · Compliance & audit

Find targeted collection folder identifiers

Generate folder identifiers for use in a compliance content search targeting specific Recoverable Items folders in primary and archive mailboxes.

Connect & set up

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

Connect-ExchangeOnline
Connect-IPPSSession

Run it

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

$Modules = Get-Module | Select-Object -ExpandProperty Name
If ("ExchangeOnlineManagement" -notin $Modules) { Write-Host "Please connect to the Exchange Online management module and restart." ; break }
$Target = Read-Host "Enter the user principal name of the mailbox to check"
[array]$Mbx = Get-ExoMailbox -Identity $Target -ErrorAction SilentlyContinue
If ($Mbx.count -ne 1) { Write-Host ("Sorry - can't find a mailbox for {0}" -f $Target) ; break }
$UserAccount = $Mbx.UserPrincipalName
$FolderQueries = @()
$ArchiveQueries = @()
$Encoding = [System.Text.Encoding]::GetEncoding("us-ascii")
$Nibbler = $Encoding.GetBytes("0123456789ABCDEF")
Write-Host ("Checking primary mailbox for {0}" -f $UserAccount)
[array]$Folders = Get-ExoMailboxFolderStatistics -Identity $UserAccount -FolderScope RecoverableItems
If (!($Folders)) { Write-Host ("Unable to retrieve mailbox folder statistics for {0} - exiting" -f $UserAccount) ; break }
ForEach ($Folder in $Folders) {
$FolderPath = $Folder.FolderPath;
If (($FolderPath -eq "/Versions") -or ($FolderPath -eq "/Deletions") -or ($FolderPath -eq "/Purges") -or ($FolderPath -eq "/DiscoveryHolds") -or ($FolderPath -eq "/SubstrateHolds")) {
$FolderId = $Folder.FolderId
$FolderIdBytes = [Convert]::FromBase64String($folderId)
$IndexIdBytes = New-Object byte[] 48
$IndexIdIdx=0
$FolderIdBytes | Select-object -skip 23 -First 24 | %{$indexIdBytes[$indexIdIdx++]=$nibbler[$_ -shr 4];$indexIdBytes[$indexIdIdx++]=$nibbler[$_ -band 0xF]}
$FolderQuery = "folderid:$($encoding.GetString($indexIdBytes))";
$FolderDetails = New-Object PSObject
Add-Member -InputObject $FolderDetails -MemberType NoteProperty -Name FolderPath -Value $FolderPath
Add-Member -InputObject $FolderDetails -MemberType NoteProperty -Name FolderQuery -Value $folderQuery
$FolderQueries += $FolderDetails
} # End if
} # End Foreach
# Content search will always process an archive mailbox if one is available, so we need to fetch the identifiers for the target folders in the archive too
$ArchiveDatabase = Get-ExoMailbox -Identity $UserAccount -PropertySet Archive | Select-Object -ExpandProperty ArchiveDatabase
If ($ArchiveDatabase) { # We need to process archive folders too
Write-Host ("Checking archive mailbox for {0}" -f $UserAccount)
[array]$Folders = Get-ExoMailboxFolderStatistics -Identity $UserAccount -FolderScope RecoverableItems -Archive
If (!($Folders)) { Write-Host ("Unable to retrieve archive mailbox folder statistics for {0}" -f $UserAccount) }
ForEach ($Folder in $Folders) {
$FolderPath = $Folder.FolderPath;
If (($FolderPath -eq "/Versions") -or ($FolderPath -eq "/Deletions") -or ($FolderPath -eq "/Purges") -or ($FolderPath -eq "/DiscoveryHolds") -or ($FolderPath -eq "/SubstrateHolds")) {
$FolderId = $Folder.FolderId
$FolderIdBytes = [Convert]::FromBase64String($folderId)
$IndexIdBytes = New-Object byte[] 48
$IndexIdIdx=0
$FolderIdBytes | Select-object -skip 23 -First 24 | %{$indexIdBytes[$indexIdIdx++]=$nibbler[$_ -shr 4];$indexIdBytes[$indexIdIdx++]=$nibbler[$_ -band 0xF]}
$FolderQuery = "folderid:$($encoding.GetString($indexIdBytes))";
$FolderDetails = New-Object PSObject
Add-Member -InputObject $FolderDetails -MemberType NoteProperty -Name FolderPath -Value $FolderPath
Add-Member -InputObject $FolderDetails -MemberType NoteProperty -Name FolderQuery -Value $folderQuery
$ArchiveQueries += $FolderDetails
} # End if Folders
} # End Foreach folders
} # End if Archive
Write-Host ""
Write-Host "Folder identifiers to use for content search"
Write-Host "--------------------------------------------"
Write-Host ""
Write-Host "Primary mailbox"
$FolderQueries | Format-Table
$KQLQuery = $FolderQueries.FolderQuery -join " OR "
Write-Host ""
If ($ArchiveDatabase) {
Write-Host "Archive mailbox"
$ArchiveQueries | Format-Table
$KQLQuery2 = $ArchiveQueries.FolderQuery -join " OR "
$KQLQuery = $KQLQuery + " OR " + $KQLQuery2
}
Write-Host ("And here's the KQL query to insert into the content search: {0}" -f $KQLQuery)
Connect-IPPSSession
Write-Host "Creating the compliance search..."
$SearchName = "Focused Mailbox Search"
Remove-ComplianceSearch -Identity $SearchName -Confirm:$False -ErrorAction SilentlyContinue
New-ComplianceSearch -Name $SearchName -ContentMatchQuery $KQLQuery -Description ("Focused folder search for mailbox {0}" -f $UserAccount) -ExchangeLocation $UserAccount
Write-Host "Starting search"
Start-ComplianceSearch -Identity $SearchName
Do {
Write-Host ("Waiting for search {0} to comlete..." -f $SearchName)
Start-Sleep -Seconds 5
$ComplianceSearch = Get-ComplianceSearch -Identity $SearchName
} While ($ComplianceSearch.Status -ne 'Completed')
Write-Host ("Search found {0} items in mailbox {1}" -f $ComplianceSearch.Items, $UserAccount)
Write-Host ""
Attribution