
Have I been Pwned is a great initiative run by Troy Hunt, a renowned security professional. The Have I been Pwned site helps you check whether your account details have been exposed in a data breach. The site also has an easy to use API that you can query from your own applications and scripts.
The following scripts will check your Office 365 accounts and their aliases against the Have I been Pwned API. If a breach is found to be associated with an email address in Office 365, the relevant details are exported to a CSV.
Please note: A returned result doesn’t mean that the account has been breached, or that the user’s Office 365 data has been compromised. The API is only letting us know if the email address we’re checking has been associated with a particular breach. It’s up to you and your users to determine the significance of this information. If users are using different passwords, have changed their passwords since the breach occurred, or the content exposed in the breach isn’t enough to cause them harm, then there may be no reason to worry.
Some things to keep in mind
- The Have I Been Pwned API is no longer free to use. You’ll need to purchase an API key from here - right now it’s about $3.50 USD per month.
- The API itself is pretty strict on rate limiting, so this script can be quite long-running. The time between calls is 2 seconds, so expect it to run for hours if you’re checking thousands of addresses.
- This script will also check the addresses of external users in your tenant. If you’d like to exclude these, replace the line that intialises the $users variable with this:
$users = Get-msoluser -TenantId $customer.TenantId -All | Where-Object {$_.UserPrincipalName -notmatch "#EXT#"} - If you’d like to include the .onmicrosoft.com aliases in your script, just remove the following from the Where-Object filter:
-and $_ -notmatch ".onmicrosoft.com"
How to check Office 365 account email addresses against known breaches via PowerShell
- Double click on either of the scripts below to select it all
- Copy and paste it into Visual Studio Code and save it as a .ps1 file
- Update the $HIBPAPIKey variable with the API key you purchased from here.
- Run it by pressing F5
- If you’re using the single tenant version, sign in as an Office 365 global admin. If you’re using the delegated tenant version, sign in as an account with delegated admin access to customer tenants.
- Wait for the script to complete. If you’re running this across a number of tenants, you’ll probably be waiting a while.
- A CSV of it’s results will be saved to C:\temp\BreachedAccounts.csv as it processes. If you want to check the CSV as it processes, make a copy of it and open the copy to prevent write errors.

Powershell Script to check a single Office 365 tenant’s emails for known associated breaches
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Connect-MsolService
$HIBPAPIKey = "YOURHIBPAPIKEY"
$headers = @{
"hibp-api-key" = $HIBPAPIKey
}
$baseUri = "https://haveibeenpwned.com/api/v3"
$users = Get-msoluser -All
foreach ($user in $users) {
$emails = $user.proxyaddresses | Where-Object {$_ -match "smtp:" -and $_ -notmatch ".onmicrosoft.com"}
$emails | ForEach-Object {
$email = ($_ -split ":")[1]
$uriEncodeEmail = [uri]::EscapeDataString($email)
$uri = "$baseUri/breachedaccount/$uriEncodeEmail"
$breachResult = $null
try {
[array]$breachResult = Invoke-RestMethod -Uri $uri -Headers $headers -ErrorAction SilentlyContinue
}
catch {
if($error[0].Exception.response.StatusCode -match "NotFound"){
Write-Host "No Breach detected for $email" -ForegroundColor Green
}else{
Write-Host "Cannot retrieve results due to rate limiting or suspect IP. You may need to try a different computer"
}
}
if ($breachResult) {
foreach ($breach in $breachResult) {
$breachObject = [ordered]@{
Email = $email
UserPrincipalName = $user.UserPrincipalName
LastPasswordChange = $user.LastPasswordChangeTimestamp
BreachName = $breach.Name
BreachTitle = $breach.Title
BreachDate = $breach.BreachDate
BreachAdded = $breach.AddedDate
BreachDescription = $breach.Description
BreachDataClasses = ($breach.dataclasses -join ", ")
IsVerified = $breach.IsVerified
IsFabricated = $breach.IsFabricated
IsActive = $breach.IsActive
IsRetired = $breach.IsRetired
IsSpamList = $breach.IsSpamList
}
$breachObject = New-Object PSobject -Property $breachObject
$breachObject | Export-csv C:\temp\SingleTenantBreachedAccounts.csv -NoTypeInformation -Append
Write-Host "Breach detected for $email - $($breach.name)" -ForegroundColor Yellow
Write-Host $breach.description -ForegroundColor DarkYellow
}
}
Start-sleep -Milliseconds 2000
}
}
Powershell Script to check multiple Office 365 tenant’s accounts for known associated breaches using delegated administration
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Connect-MsolService
$HIBPAPIKey = "YOURHIBPAPIKEY"
$headers = @{
"hibp-api-key" = $HIBPAPIKey
}
$baseUri = "https://haveibeenpwned.com/api/v3"
$customers = Get-MsolPartnerContract -All
foreach ($customer in $customers) {
$users = Get-msoluser -TenantId $customer.TenantId -All
foreach ($user in $users) {
$emails = $user.proxyaddresses | Where-Object {$_ -match "smtp:" -and $_ -notmatch ".onmicrosoft.com"}
$emails | ForEach-Object {
$email = ($_ -split ":")[1]
$uriEncodeEmail = [uri]::EscapeDataString($email)
$uri = "$baseUri/breachedaccount/$uriEncodeEmail"
$breachResult = $null
try {
[array]$breachResult = Invoke-RestMethod -Uri $uri -Headers $headers -ErrorAction SilentlyContinue
}
catch {
if ($error[0].Exception.response.StatusCode -match "NotFound") {
Write-Host "No Breach detected for $email" -ForegroundColor Green
}
else {
Write-Host "Cannot retrieve results due to rate limiting or suspect IP. You may need to try a different computer"
}
}
if ($breachResult) {
foreach ($breach in $breachResult) {
$breachObject = [ordered]@{
CustomerName = $customer.Name
TenantId = $customer.TenantId
Email = $email
UserPrincipalName = $user.UserPrincipalName
LastPasswordChange = $user.LastPasswordChangeTimestamp
BreachName = $breach.Name
BreachTitle = $breach.Title
BreachDate = $breach.BreachDate
BreachAdded = $breach.AddedDate
BreachDescription = $breach.Description
BreachDataClasses = ($breach.dataclasses -join ", ")
IsVerified = $breach.IsVerified
IsFabricated = $breach.IsFabricated
IsActive = $breach.IsActive
IsRetired = $breach.IsRetired
IsSpamList = $breach.IsSpamList
}
$breachObject = New-Object PSobject -Property $breachObject
$breachObject | Export-csv C:\temp\BreachedAccounts.csv -NoTypeInformation -Append
Write-Host "Breach detected for $email - $($breach.name)" -ForegroundColor Yellow
Write-Host $breach.description -ForegroundColor DarkYellow
}
}
Start-sleep -Milliseconds 2000
}
}
}
