It can sometimes be a challenge in maintaining control over email domain usage is crucial for security and brand consistency. I recently developed a PowerShell solution to address a common challenge: monitoring when mailboxes are created using non-standard or unapproved domains in Exchange Online.
The Challenge
Organizations often have multiple accepted domains in Exchange Online - some for primary business use, others for legacy systems, acquisitions, or specific services. However, not all accepted domains should be actively used for new mailboxes. Without proper monitoring, mailboxes can be created with non-standard domains, leading to:
- Brand inconsistency
- Security vulnerabilities
- Compliance issues
- Email deliverability problems
I needed a solution that would automatically detect when mailboxes were created outside our approved domain list and alert the appropriate teams.
HTML Email Visual
This is an example of the HTML email
The Solution Architecture
The script that connects to Exchange Online and implements an intelligent tracking system. The script maintains state between runs, ensuring we only get notified about genuinely new mailboxes rather than being bombarded with the same information repeatedly.
Core Components
The solution consists of three main components:
- Domain Filtering Logic - Identifies which domains to monitor
- State Tracking System - JSON-based persistence to track previously discovered mailboxes
- Notification System - HTML email reports with CSV attachments
Implementation Deep Dive
Setting Up Domain Exclusions
First, I defined the approved domains that should be excluded from monitoring:
# Define excluded domains
$excludedDomains = @(
"bearco.onmicrosoft.com",
"croucher.cloud"
)
These are our standard corporate domains where mailbox creation is expected and approved.
Efficient Domain Querying
Instead of checking each domain individually (which would be slow), I built a single filter query that retrieves all relevant mailboxes in one operation:
# Get all accepted domains and filter out excluded ones
$domainsToCheck = Get-AcceptedDomain |
Select-Object -ExpandProperty DomainName |
Where-Object { $_ -notin $excludedDomains }
# Build filter for all domains at once
$filter = ($domainsToCheck | ForEach-Object {
"EmailAddresses -like '*@$_'"
}) -join ' -or '
# Get all mailboxes matching any of the non-excluded domains
$mailboxes = Get-Mailbox -ResultSize Unlimited -Filter $filter
This approach significantly improves performance by making a single query to Exchange Online rather than multiple queries per domain.
Implementing State Tracking
The key to making this solution practical was implementing state tracking. I used a JSON file to maintain a record of all previously discovered mailboxes:
# Check if this is first run
$isFirstRun = -not (Test-Path $jsonPath)
# Load previous results
$previousMailboxes = @{}
if (-not $isFirstRun) {
$jsonContent = Get-Content $jsonPath -Raw | ConvertFrom-Json
foreach ($prop in $jsonContent.PSObject.Properties) {
$previousMailboxes[$prop.Name] = $prop.Value
}
}
This allows the script to differentiate between:
- First run: Establishes a baseline of all existing mailboxes
- Subsequent runs: Only reports genuinely new mailboxes
Processing and Categorizing Mailboxes
For each mailbox found, I capture comprehensive information and determine which non-standard domain it's using:
foreach ($mailbox in $mailboxes) {
# Find which domain this mailbox uses
$matchedDomain = ""
foreach ($email in $mailbox.EmailAddresses) {
foreach ($domain in $domainsToCheck) {
if ($email -like "*@$domain") {
$matchedDomain = $domain
break
}
}
if ($matchedDomain) { break }
}
$mailboxData = @{
UserPrincipalName = $mailbox.UserPrincipalName
DisplayName = $mailbox.DisplayName
PrimarySmtpAddress = $mailbox.PrimarySmtpAddress
Domain = $matchedDomain
MailboxType = $mailbox.RecipientTypeDetails
WhenCreated = $mailbox.WhenCreated
FirstSeen = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
}
}
Smart Notification Logic
The script only sends notifications when action is needed:
# Save CSV and send email only if there are new mailboxes or it's the first run
if ($isFirstRun -or $newMailboxes.Count -gt 0) {
# Export to CSV
if ($isFirstRun) {
# First run - export all mailboxes as baseline
$allMailboxes.Values | ForEach-Object { [PSCustomObject]$_ } |
Export-Csv -Path $csvPath -NoTypeInformation
} else {
# Subsequent runs - export only new
$newMailboxes | Export-Csv -Path $csvPath -NoTypeInformation
}
}
This prevents alert fatigue by ensuring emails are only sent when there's something to report.
HTML Email Reports
I implemented HTML-formatted emails that provide a clear, professional summary of findings:
$htmlBody = @"
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
color: #333;
line-height: 1.6;
}
.header {
background-color: #004080;
color: white;
padding: 20px;
border-radius: 5px 5px 0 0;
}
.summary {
background-color: white;
padding: 15px;
margin: 15px 0;
border-radius: 5px;
border: 1px solid #e0e0e0;
}
</style>
</head>
<body>
<div class="container">
<h2>Exchange Domain Audit Report</h2>
<p><strong>New Mailboxes Since Last Run:</strong>
<span class='highlight'>$($newMailboxes.Count)</span></p>
</div>
</body>
</html>
"@
The email includes:
- A summary of findings
- Domain statistics showing which domains are being used
- Professional formatting without unnecessary graphics
- CSV attachment with detailed mailbox information
Running the Script
The script can be executed manually or scheduled via Task Scheduler:
.\ExchangeDomainAudit.ps1
Output example when new mailboxes are found:
Connecting to Exchange Online...
Getting accepted domains...
Domains to check: pokebearswithsticks.com, bythepowerofgreyskull.com
Searching for mailboxes...
Found 41 mailboxes with non-excluded domains
RESULTS:
Total mailboxes: 41
NEW mailboxes: 3
New mailboxes saved to: NewMailboxes_20251205_140000.csv
Sending email report...
Email sent successfully to: lee@croucher.cloud
Conclusion
This automated monitoring solution provides a practical approach to maintaining domain compliance in Exchange Online. By combining intelligent state tracking with targeted notifications, it delivers exactly the information needed without creating noise.