← All scripts

Email security & monitoring

Dark Web Breach Check: Partner Center edition

As above, with Microsoft Partner Center support so you can pick and scan any client tenant you manage. Needs a paid HIBP API key.

Adapted from: Adapted from CyberDrain (Kelvin Tegelaar) ↗

Download .ps1211 lines · PowerShell

Replace generic placeholder values (tenant, domain, secrets) for your own environment before running. Read it first and test safely.

<#
.SYNOPSIS
    Checks every user in a Microsoft 365 tenant (direct, or via Partner Center for any
    managed client) against Have I Been Pwned, producing a searchable HTML + CSV report.

.DESCRIPTION
    Two login options: Partner Center (pick any client tenant you manage) or a direct
    Microsoft Graph login to a single tenant. Queries HIBP per user; optional Shodan
    lookup if $ShodanAPIKey is set. Requires a paid HIBP API key and the PartnerCenter
    and/or Microsoft.Graph modules.

.NOTES
    ATTRIBUTION: adapted from Kelvin Tegelaar's public breach-monitoring script at
    CyberDrain.com. Original author retains copyright; used/adapted with credit.
    SANITISED FOR PUBLIC RELEASE: hardcoded HIBP API key replaced with a placeholder.
    Supply your own key (better: from a secret store) - do not hardcode it in a shared script.
#>
Add-Type -AssemblyName System.Web

function Get-BreachInfo {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]$EmailAddress,
        [Parameter(Mandatory = $true)]$HaveIBeenPwnedKey,
        [Parameter(Mandatory = $true)]$Outputfile
    )
    $head = @"
<script>
function myFunction() {
    const filter = document.querySelector('#myInput').value.toUpperCase();
    const trs = document.querySelectorAll('table tr:not(.header)');
    trs.forEach(tr => tr.style.display = [...tr.children].find(td => td.innerHTML.toUpperCase().includes(filter)) ? '' : 'none');
  }</script>
<Title>Dark Web / Breach Report</Title>
<style>
body { background-color:#E5E4E2;
      font-family:Monospace;
      font-size:10pt; }
td, th { border:0px solid black; 
        border-collapse:collapse;
        white-space:pre; }
th { color:white;
    background-color:black; }
table, tr, td, th {
     padding: 2px; 
     margin: 0px;
     white-space:pre; }
tr:nth-child(odd) {background-color: lightgray}
table { width:95%;margin-left:5px; margin-bottom:20px; }
h2 {
font-family:Tahoma;
color:#6D7B8D;
}
.footer 
{ color:green; 
 margin-left:10px; 
 font-family:Tahoma;
 font-size:8pt;
 font-style:italic;
}
#myInput {
  background-image: url('https://www.w3schools.com/css/searchicon.png'); /* Add a search icon to input */
  background-position: 10px 12px; /* Position the search icon */
  background-repeat: no-repeat; /* Do not repeat the icon image */
  width: 50%; /* Full-width */
  font-size: 16px; /* Increase font-size */
  padding: 12px 20px 12px 40px; /* Add some padding */
  border: 1px solid #ddd; /* Add a grey border */
  margin-bottom: 12px; /* Add some space below the input */
}
</style>
"@
    
    $PreContent = @"
<H1> Breach logbook</H1> <br>
    
This log contains all breaches found for the e-mail addresses in your Microsoft tenant. You can use the search to find specific e-mail addresses.
<br/>
<br/>
     
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search...">
"@
    $HIBPHeader = @{'hibp-api-key' = $HaveIBeenPwnedKey }
    $HIBP_Subscription = Invoke-RestMethod -Uri 'https://haveibeenpwned.com/api/v3/subscription/status' -Headers $HIBPHeader -UserAgent 'CyberDrain.com PowerShell Breach Script'
    $Delay = 60 / $HIBP_Subscription.rpm + 0.5
    $est_tot_time = New-TimeSpan -Seconds ($Delay * $EmailAddress.count)
    write-host "  Retrieving Breach Info" -ForegroundColor Green
    $UserList = $EmailAddress
    $index = 0
    $breachcount = 0
    "HIBP Key limit $($HIBP_Subscription.rpm) Checks per minute * $($EmailAddress.count) emails = $($est_tot_time.Minutes):$($est_tot_time.Seconds) total scan time"

    $HIBPList = foreach ($User in $UserList) {
    $index++
    Write-Progress -Activity "Search in Progress" -Status "Scanning $User - $([math]::Round($index/$UserList.count*100))% Complete:" -PercentComplete ($index/$UserList.count*100) -SecondsRemaining ($est_tot_time.TotalSeconds - ($Delay * $index));
        try {
            $Breaches = $null
            $Breaches = Invoke-RestMethod -Uri "https://haveibeenpwned.com/api/v3/breachedaccount/$($user)?truncateResponse=false" -Headers $HIBPHeader -UserAgent 'CyberDrain.com PowerShell Breach Script'
        }
        catch {
            if ($_.Exception.Response.StatusCode.value__ -eq '404') {  } else { write-error "$($_.Exception.message)" }
        }
        start-sleep 7
        $breachcount += $Breaches.count
        foreach ($Breach in $Breaches) {
            [PSCustomObject]@{
                Username              = $user
                'Name'                = $Breach.name
                'Domain name'         = $breach.Domain
                'Date'                = $Breach.Breachdate
                'Verified by experts' = if ($Breach.isverified) { 'Yes' } else { 'No' }
                'Leaked data'         = $Breach.DataClasses -join ', '
                'Description'         = $Breach.Description
            }
        }
    }
    $BreachListHTML = $HIBPList | ConvertTo-Html -Fragment -PreContent '<h2>Breaches</h2><br> A "breach" is an incident where data is inadvertently exposed in a vulnerable system, usually due to insufficient access controls or security weaknesses in the software. HIBP aggregates breaches and enables people to assess where their personal data has been exposed.<br>' | Out-String
    $HIBPList | Export-Csv -Path "$OutputFile.csv"
 if ($null -ne $ShodanAPIKey){
    write-host "      Getting Shodan information." -ForegroundColor Green
    foreach ($Domain in $IPs) {
        $ShodanQuery = (Invoke-RestMethod -Uri "https://api.shodan.io/shodan/host/search?key=$($ShodanAPIKey)&query=$Domain" -UserAgent 'CyberDrain.com PowerShell Breach Script').matches
        foreach ($FoundItem in $ShodanQuery) {
            [PSCustomObject]@{
                'Searched for'    = $Domain
                'Found Product'   = $FoundItem.product
                'Found open port' = $FoundItem.port
                'Found IP'        = $FoundItem.ip_str
                'Found Domain'    = $FoundItem.domain
            }
 
        }
    }
    }
    $head, $PreContent, [System.Web.HttpUtility]::HtmlDecode($BreachListHTML), $ShodanHTML | Out-File $Outputfile
    Write-Progress -Activity "Search in Progress" -Completed
    "$breachcount Account breaches found"
}

$loginSuccess = $false

Write-Host "Please type number or leave blank for option 1, then press [Enter]`n[1] Partner center login | use your own login`n[2] Direct Login | Use the clients credentials to login"
$option = Read-Host 'Option: 1/2/[blank]'

if ($option -eq 2){
    $Module= 'Microsoft.Graph'
    if((Get-Module -Name $Module -ListAvailable).count -eq 0){
      Write-Host  "Module $Module is missing, running a one off install now, this may take a few minutes`nInstall using below command: `nInstall-Module -Scope CurrentUser -Name $Module"  -ForegroundColor yellow
      Install-Module -Name $Module -AllowClobber -Scope CurrentUser
    }

    if (Connect-MgGraph -ContextScope Process -Scopes 'User.Read.All'){
    $loginSuccess = $true

    $userlist = Get-MgUser -Filter "usertype ne 'Guest'" -All -ConsistencyLevel eventual -CountVariable count
    $userlist = $userlist.UserPrincipalName

    $OrganiseationName = (Get-MgOrganization).DisplayName
    }
}else {
    <# Action when all if and elseif conditions are false #>

$Module= 'PartnerCenter'
    if((Get-Module -Name $Module -ListAvailable).count -eq 0){
    Write-Host  "Module $Module is missing, running a one off install now, this may take a minute`nInstall using below command: `nInstall-Module -Scope CurrentUser -Name $Module"  -ForegroundColor yellow
    Install-Module -Name $Module -AllowClobber -Scope CurrentUser
    }
    if (Connect-PartnerCenter){
    $loginSuccess = $true

    if ($option -eq 5){#Own/self tenant
        $us = Get-PartnerOrganizationProfile
        $OrganiseationName = $us.CompanyName
        $userlist = Get-PartnerCustomerUser -CustomerId $us.TenantId
        
    }else{
        ""
        "Customer selection list opened in a seperate window, it may have opened in the background"
        $customer = Get-PartnerCustomer | Out-GridView -OutputMode Single
        $customer
        $OrganiseationName = $customer.Name

        $userlist = Get-PartnerCustomerUser -CustomerId $customer.CustomerId
    }

    $userlist = $userlist.UserPrincipalName | Where-Object {$_ -notlike '*#EXT#@*'}
    }
}



If ($loginSuccess){

    #Install-Module -Name AzureAD
    #Update-Module -Name AzureAD
    #Connect-AzureAD
    #-ConsistencyLevel eventual -CountVariable count are required for ne (Not Equal) to work https://learn.microsoft.com/en-us/graph/aad-advanced-queries?tabs=http

    #$userlist = (Get-AzureADUser).UserPrincipalName | where {$_ -NotLike '*.onmicrosoft.com'}
    if ($userlist.count -ne 0){
        "Scanning $($userlist.count) Email accounts"
        $OutputFile = "$($env:USERPROFILE)\Downloads\Breach Report $OrganiseationName $(Get-date -Format "yyyy-MM-dd").html"

        Get-BreachInfo -EmailAddress $userlist -HaveIBeenPwnedKey '<HAVEIBEENPWNED_API_KEY>' -Outputfile $OutputFile

        Invoke-Item $OutputFile
    }
}else{
    'Login failure, not scanning.'
}