Entra / Microsoft 365 · Exchange Online
Find junk email sender domains
Find domains that send junk email to your tenant by examining items in the Junk Email folder of user and shared mailboxes.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-MgGraph -TenantId $TenantId -ClientId $AppId -CertificateThumbprint $Thumbprint -NoWelcomeConnect-ExchangeOnline -ShowBanner:$false
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([string] $TenantId = "",[string] $AppId = "",[string] $DestinationEmailAddress = "")Function Get-PublicSuffix {[CmdletBinding()]param(# Domain name (e.g., "11.pen-and-sword.uk", "www.bbc.co.uk")[Parameter(Mandatory, ValueFromPipeline)][string]$Domain,# Return registrable domain (eTLD+1) instead of just the public suffix[switch]$Registrable,# Force re-download of the Public Suffix List[switch]$RefreshSuffixList)begin {# Cache PSL in script scope for the sessionif (-not $script:__PSL -or $RefreshSuffixList) {$uri = 'https://publicsuffix.org/list/public_suffix_list.dat'$raw = Invoke-WebRequest -Uri $uri -UseBasicParsing | Select-Object -ExpandProperty Content -ErrorAction Stop$rules = $raw -split "`n" | ForEach-Object {$t = $_.Trim()if ($t -and -not $t.StartsWith('//')) { $t }}$script:__PSL = [PSCustomObject]@{Exceptions = $rules | Where-Object { $_.StartsWith('!') } | ForEach-Object { $_.Substring(1) }Normals = $rules | Where-Object { -not $_.StartsWith('!') }}}$idn = [System.Globalization.IdnMapping]::new()}process {if ([string]::IsNullOrWhiteSpace($Domain)) { return $null }# Normalize domain$trimmed = $Domain.Trim().TrimEnd('.').ToLowerInvariant()if ($trimmed -match '^(?:\d{1,3}\.){3}\d{1,3}$') { return $null } # IP address check$labels = ($trimmed -split '\.') | ForEach-Object { try { $idn.GetAscii($_) } catch { $_ } }if ($labels.Count -eq 0) { return $null }# Helper: rule matchfunction Test-RuleMatch($ruleLabels, $domainLabels) {$ri = $ruleLabels.Count - 1$di = $domainLabels.Count - 1while ($ri -ge 0 -and $di -ge 0) {$rule = $ruleLabels[$ri]$dom = $domainLabels[$di]if ($rule -eq '*') { $ri--; $di--; continue }if ($rule -ieq $dom) { $ri--; $di--; continue }return $false}return ($ri -lt 0)}# PSL matching$bestLen = 1$matchedByExcept = $falseforeach ($ex in $script:__PSL.Exceptions) {$exLabels = $ex -split '\.'if (Test-RuleMatch $exLabels $labels) {$len = [Math]::Max($exLabels.Count - 1, 1)if ($len -gt $bestLen) { $bestLen = $len; $matchedByExcept = $true }}}if (-not $matchedByExcept) {foreach ($nr in $script:__PSL.Normals) {$nrLabels = $nr -split '\.'if (Test-RuleMatch $nrLabels $labels) {$len = $nrLabels.Countif ($len -gt $bestLen) { $bestLen = $len }}}}$suffix = ($labels[($labels.Count - $bestLen)..($labels.Count - 1)] -join '.')if ($Registrable) {if ($labels.Count -gt $bestLen) {return ($labels[($labels.Count - ($bestLen + 1))..($labels.Count - 1)] -join '.')} else {return $null}} else {return $suffix}}}# The script can only run in app-only mode, so define the settings to connect interactively using a certificate. If using an app, make sure# that the values for the appid, tenantid, and certificate thumbprint variables below match your app registration.# Alternatively, you can run this code in Azure Automation and use a managed identity to authenticate$Thumbprint = '0CF6CE3F3548FD73E7AC8CF20226ED447E125C71'# For interactive use, the signed-in user must be an Exchange administratorConnect-MgGraph -TenantId $TenantId -ClientId $AppId -CertificateThumbprint $Thumbprint -NoWelcomeConnect-ExchangeOnline -ShowBanner:$false# Change these variables to select the mailbox the message will come from and the destination SMTP address$MsgFrom = 'noreply@office365itpros.com'# Get mailboxes - users and shared[array]$Mbx = Get-ExoMailbox -RecipientTypeDetails UserMailbox, SharedMailbox -ResultSize Unlimited | Sort-Object DisplayNameIf (!($Mbx)) {Write-Host "No user or shared mailboxes found" -ForegroundColor RedBreak} Else {Write-Host ("{0} user and shared mailboxes found" -f $Mbx.Count) -ForegroundColor Green}# Find the domains for junk email senders$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($M in $Mbx) {Write-Host ("Processing mailbox {0}" -f $M.DisplayName)$JunkEmailFolder = Get-MgUserMailFolder -UserId $M.ExternalDirectoryObjectId -MailFolderId "junkemail"If ($null -eq $JunkEmailFolder) {Write-Host ("Failed to get Junk Email folder for {0}" -f $M.DisplayName) -ForegroundColor RedContinue}[array]$MailItems = Get-MgUserMailFolderMessage -UserId $M.ExternalDirectoryObjectId -MailFolderId $JunkEmailFolder.Id -All -PageSize 500 `-Property SentDateTime, Sender, SubjectIf (!($MailItems)) {Write-Host ("No items found in the Junk Email folder for {0}" -f $M.DisplayName) -ForegroundColor YellowContinue} Else {Write-Host ("{0} items found in the Junk Email folder for {1}" -f $MailItems.Count, $M.DisplayName) -ForegroundColor GreenForEach ($MailItem in $MailItems) {$EmailDomain = ($MailItem.Sender.emailAddress.Address -split '@')[1]# Extract the root domain unless it's a .onmicrosoft.com service domainIf ($EmailDomain -Notlike "*.onmicrosoft.com") {$EmailDomain = Get-PublicSuffix -Domain $EmailDomain -Registrable}# Report each item found in the Junk Email folder$ReportItem = [PSCustomObject]@{DisplayName = $M.DisplayNameId = $M.ExternalDirectoryObjectIdUserPrincipalName = $M.UserPrincipalNameMailboxType = $M.RecipientTypeDetails'Junk Mail Items' = $MailItems.Count'Junk Mail Date' = Get-Date $MailItem.SentDateTime -format 'dd-MMM-yyyy HH:mm''Junk Mail Sender' = $MailItem.Sender.emailAddress.Name'Junk Mail Email' = $MailItem.Sender.emailAddress.Address'Junk Mail Subject' = $MailItem.Subject'Junk Mail Domain' = $EmailDomain}$Report.Add($ReportItem)}If ($DeleteItemsNow) {Write-Host ("Deleting {0} spammy items from the Junk Email folder for {1}" -f $MailItems.Count, $M.DisplayName) -ForegroundColor YellowForEach ($MailItem in $MailItems) {Try {Remove-MgUserMailFolderMessage -UserId $M.ExternalDirectoryObjectId -MailFolderId $JunkEmailFolder.Id -MessageId $MailItem.Id -ErrorAction Stop} Catch {Write-Host ("Failed to delete item {0} from the Junk Email folder for {1}" -f $MailItem.Id, $M.DisplayName) -ForegroundColor Red}}}}}# Extract the set of domains[array]$JunkEmailDomains = $Report | Group-Object 'Junk Mail Domain' -NoElement | Select-Object -ExpandProperty Name# Remove consumer domains like gmail.com, outlook.com, yahoo.com, etc.[array]$ConsumerDomains = @('gmail.com','outlook.com','yahoo.com','hotmail.com','live.com','aol.com','icloud.com','protonmail.com','zoho.com','gmx.com','msn.com')$JunkEmailDomains = $JunkEmailDomains | Where-Object {$_ -and ($_ -notin $ConsumerDomains)} | Sort-Object -UniqueWrite-Host "Generating report..."If (Get-Module ImportExcel -ListAvailable) {$ExcelGenerated = $TrueImport-Module ImportExcel -ErrorAction SilentlyContinue$ExcelOutputFile = ((New-Object -ComObject Shell.Application).Namespace('shell:Downloads').Self.Path) + "\Junk Email Domains.xlsx"If (Test-Path $ExcelOutputFile) {Remove-Item $ExcelOutputFile -ErrorAction SilentlyContinue}$JunkEmailDomains | Export-Excel -Path $ExcelOutputFile -WorksheetName "JunkEmailDomains" -Title ("Junk Email Domains {0}" -f (Get-Date -format 'dd-MMM-yyyy')) `-TitleBold -TableName "JunkEmailDomains"} Else {$CSVOutputFile = ((New-Object -ComObject Shell.Application).Namespace('shell:Downloads').Self.Path) + "\Junk Email Domains.CSV"$JunkEmailDomains | Export-Csv -Path $CSVOutputFile -NoTypeInformation -Encoding Utf8}If ($ExcelGenerated) {Write-Host ("An Excel report of Junk Email Domains is available in {0}" -f $ExcelOutputFile)$OutputFile = $ExcelOutputFile} Else {Write-Host ("A CSV report of Junk Email Domains is available in {0}" -f $CSVOutputFile)$OutputFile = $CSVOutputFile}# Encode the output file so that it can be added as an attachment to an email$EncodedAttachmentFile = [Convert]::ToBase64String([IO.File]::ReadAllBytes($OutputFile))# Create the attachments array$MsgAttachments = @(@{'@odata.type' = '#microsoft.graph.fileAttachment'Name = (Split-Path $OutputFile -Leaf)ContentBytes = $EncodedAttachmentFileContentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'})# Create a string of domains to block for inclusion in the email body[string]$JunkEmailDomainsOutput = $JunkEmailDomains -join "', '"$TransportRuleName = "'Quarantine Traffic from Junk Email Domains'"# Define the message recipient (see earlier)$ToRecipient = @{}$ToRecipient.Add("emailAddress",@{'address'=$DestinationEmailAddress})[array]$MsgTo = $ToRecipient# Define the message subject$MsgSubject = "Important: Junk Email Domains Report"# Create the HTML content$HtmlMsg = "</body></html><p>The output file for the <b>Junk Email Domains Report</b> are attached to this message. Please review the information at your convenience and consider creating a transport rule to block these domains</p>"$HtmlMsg = $HtmlMsg + "<p>You can use PowerShell commands like this to create a transport rule to block these domains:<p></p>"$HtmlMsg = $HtmlMsg + "<p>New-TransportRule -Name $TransportRuleName -SenderDomainIs '$JunkEmailDomainsOutput' -Mode Enforce -Quarantine 1 -SenderAddressLocation HeaderOrEnvelope -Comments 'Blocks messages from domains that send junk email'</p>"# Construct the message body$MsgBody = @{}$MsgBody.Add('Content', "$($HtmlMsg)")$MsgBody.Add('ContentType','html')# Build the parameters to submit the message$Message = @{}$Message.Add('subject', $MsgSubject)$Message.Add('toRecipients', $MsgTo)$Message.Add('body', $MsgBody)$Message.Add("attachments", $MsgAttachments)$EmailParameters = @{}$EmailParameters.Add('message', $Message)$EmailParameters.Add('saveToSentItems', $true)$EmailParameters.Add('isDeliveryReceiptRequested', $true)# Send the messageTry {Send-MgUserMail -UserId $MsgFrom -BodyParameter $EmailParameters -ErrorAction StopWrite-Output ("Junk Email Domains report emailed to {0}" -f $ToRecipient.emailAddress.address)} Catch {Write-Output "Unable to send email"Write-Output $_.Exception.Message}Write-Output "All done"
Parameters
ParameterDefaultNotes
-TenantId""Microsoft Entra tenant ID for app-only Graph authentication.-AppId""Application (client) ID for the app registration used to connect.-DeleteItemsNow$falseWhen true, delete all items in each mailbox Junk Email folder after processing.-DestinationEmailAddress""Email address that receives the generated report.Attribution
Author
Office365itpros