DirectSendAudit.psm1

function Start-DirectSendAudit {
    [CmdletBinding()]
    param()

    # ----------------------------
    # Clear any existing Exchange Online sessions (silent)
    # ----------------------------
    Get-PSSession | Where-Object { $_.ComputerName -like "*.outlook.com" -or $_.ComputerName -like "*.office365.com" } | Remove-PSSession -ErrorAction SilentlyContinue

    # ----------------------------
    # Prompt for output folder
    # ----------------------------
    $defaultFolder = "C:\Temp"
    $userFolder = Read-Host "Enter output folder path or press Enter to use default [$defaultFolder]"
    if ([string]::IsNullOrWhiteSpace($userFolder)) { $OutputFolder = $defaultFolder } else { $OutputFolder = $userFolder }
    if (-not (Test-Path $OutputFolder)) { New-Item -ItemType Directory -Path $OutputFolder | Out-Null }

    # ----------------------------
    # Prompt for number of days
    # ----------------------------
    $defaultDays = 7
    $userDays = Read-Host "Enter number of days to generate the report for (1-90, default $defaultDays)"
    if ([string]::IsNullOrWhiteSpace($userDays)) { $Days = $defaultDays }
    elseif ([int]::TryParse($userDays, [ref]$null) -and $userDays -ge 1 -and $userDays -le 90) { $Days = [int]$userDays }
    else { Write-Host "Invalid input, using default $defaultDays days."; $Days = $defaultDays }

    $endDate = Get-Date
    $startDate = $endDate.AddDays(-$Days)
    Write-Host "`nGenerating report from $($startDate.ToShortDateString()) to $($endDate.ToShortDateString())" -ForegroundColor Cyan
    Write-Host "Output folder: $OutputFolder`n" -ForegroundColor Cyan

    # ----------------------------
    # Connect to Exchange Online
    # ----------------------------
    Write-Host "Connecting to Exchange Online..." -ForegroundColor Yellow
    Connect-ExchangeOnline -ErrorAction Stop

    # ----------------------------
    # Check Direct Send status
    # ----------------------------
    $orgConfig = Get-OrganizationConfig | Select-Object Identity,RejectDirectSend
    if ($orgConfig.RejectDirectSend -eq $false) { Write-Host "Direct Send appears to be enabled on your tenant." -ForegroundColor Green }
    else { Write-Host "Direct Send is not enabled on your tenant." -ForegroundColor Green }

    # ----------------------------
    # Detect tenant domains
    # ----------------------------
    $tenantDomains = (Get-AcceptedDomain).DomainName
    $primaryDomain = (Get-AcceptedDomain | Where-Object { $_.Default -eq $true }).DomainName
    Write-Host "Detected tenant domains:" -ForegroundColor Cyan -NoNewLine
    Write-Host "$($tenantDomains -join ', ')`n" -ForegroundColor White

    # ----------------------------
    # Determine notification email
    # ----------------------------
    try {
        $currentUser = (Get-ConnectionInformation).UserPrincipalName
    } catch {
        $currentUser = $null
    }

    if (-not $currentUser) {
        $currentUser = (Get-ExoMailbox -ResultSize 1).PrimarySmtpAddress.ToString()
    }

    $defaultNotifyAddress = $currentUser
    $notifyAddress = Read-Host "Enter notification email address. This must exist on the tenant. (default: $defaultNotifyAddress)"
    if ([string]::IsNullOrWhiteSpace($notifyAddress)) { $notifyAddress = $defaultNotifyAddress }

    Write-Host "`nUsing notification address: $notifyAddress`n" -ForegroundColor Cyan

    # ----------------------------
    # Start Historical Search
    # ----------------------------
    $reportTitle = "NoConnectorMessages-" + ([Guid]::NewGuid().ToString())
    Start-HistoricalSearch -ReportTitle $reportTitle `
        -StartDate $startDate `
        -EndDate $endDate `
        -ReportType ConnectorReport `
        -ConnectorType NoConnector `
        -Direction Received `
        -NotifyAddress $notifyAddress

    Write-Host "Historical search started." -ForegroundColor Cyan -NoNewLine
    Write-Host " This can take 30 minutes or longer for large data sets." -ForegroundColor White
    Write-Host "Please keep this powershell tab open." -ForegroundColor Yellow
    Write-Host "Searching for internal messages sent with no connector, indicative of Direct Send messages." -ForegroundColor Cyan
    Write-Host "Please note this report only returns results for messages sent with no connector. Further investigation may be required to determine Direct Send was used."
    Write-Host "To review all inbound messages sent without a connector please see the original Historical Message Trace report in your chosen output folder."
    Write-Host "Waiting until report is ready..." -ForegroundColor Cyan

    # ----------------------------
    # Poll until report is ready
    # ----------------------------
    $report = $null
    do {
        Start-Sleep -Seconds 60
        $report = Get-HistoricalSearch | Where-Object { $_.ReportTitle -eq $reportTitle -and $_.Status -eq "Done" }
        Write-Host "Checking report status..."
    } until ($report)

    Write-Host "`nReport is ready." -ForegroundColor Green

    # ----------------------------
    # Purview login and CSV download
    # ----------------------------
    do {
        Write-Host "`nPress " -ForegroundColor White -NoNewLine
        Write-Host "Enter " -ForegroundColor Yellow -NoNewLine
        Write-Host "to open your default browser and log in to Purview..." -ForegroundColor White
        [void][System.Console]::ReadLine()
        Start-Process $report.FileUrl

        Write-Host "After logging in to Purview the report should automatically start downloading. This can take a while."
        Write-Host "Please note this may appear as a blank browser page with 'Working...' displayed in the tab. "
        Write-Host "Once the report has finished downloading press " -ForegroundColor White -NoNewLine
        Write-Host "Enter " -ForegroundColor Yellow -NoNewLine
        Write-Host "to continue." -ForegroundColor White
        [void][System.Console]::ReadLine()

        $downloads = [Environment]::GetFolderPath("UserProfile") + "\Downloads"
        $latestCsv = Get-ChildItem -Path $downloads -Filter "*ConnectorReport*.csv" | Sort-Object LastWriteTime -Descending | Select-Object -First 1

        if ($latestCsv -eq $null) {
            Write-Host "No ConnectorReport CSV found in Downloads folder. Please wait for the download to finish and press Enter again."
        }
    } until ($latestCsv -ne $null)

    # Keep original filename when moving
    $downloadPath = Join-Path $OutputFolder $latestCsv.Name
    Move-Item -Path $latestCsv.FullName -Destination $downloadPath -Force
    Write-Host "Report moved to: $downloadPath`n" -ForegroundColor Cyan

    # ----------------------------
    # CSV filtering function
    # ----------------------------
    function Filter-CsvBySender {
        param(
            [Parameter(Mandatory=$true)][string]$InputCsv,
            [Parameter(Mandatory=$true)][string]$OutputCsv,
            [Parameter(Mandatory=$true)][string[]]$Domains
        )

        $bytes = [System.IO.File]::ReadAllBytes($InputCsv)
        $csvText = -join ($bytes | ForEach-Object {
            if (($_ -ge 32 -and $_ -le 126) -or $_ -eq 10 -or $_ -eq 13) { [char]$_ } else { '' }
        })
        $lines = $csvText -split "`r?`n"
        $header = $lines[0] -replace '[<>"]', '' -replace '^\uFEFF', ''

        $matchingLines = $lines[1..($lines.Count-1)] | Where-Object {
            $fields = $_ -split ','
            if ($fields.Count -gt 3) {
                $sender = $fields[3] -replace '[<>"]', ''
                $Domains | ForEach-Object { $sender -match [regex]::Escape($_) } | Where-Object { $_ } | Measure-Object | Select-Object -ExpandProperty Count
            } else { $false }
        }

        if ($matchingLines.Count -gt 0) {
            $filteredCsv = @($header) + $matchingLines
            $cleanedCsv = $filteredCsv | ForEach-Object { $_ -replace '[<>"]', '' }
            Set-Content -Path $OutputCsv -Value $cleanedCsv -Encoding UTF8
            Write-Host "Filtered CSV saved with $($matchingLines.Count) matching rows."
        } else {
            Write-Host "No matching rows found where sender contains any of the specified domains."
        }
    }

    # Rename filtered file based on tenant domain + timestamp
    $timestamp = Get-Date -Format "yyyyMMdd-HHmm"
    $filteredPath = Join-Path $OutputFolder "$($primaryDomain)-DirectSendReport-$timestamp.csv"
    Filter-CsvBySender -InputCsv $downloadPath -OutputCsv $filteredPath -Domains $tenantDomains

    # ----------------------------
    # Summary and optional open
    # ----------------------------
    Write-Host "`nFiltered CSV is available at: $filteredPath"
    $openNow = Read-Host "Would you like to open the filtered CSV now? (Y/N)" -ForegroundColor Cyan
    if ($openNow -in @('Y','y')) { Invoke-Item $filteredPath }

    Write-Host "`n--- Summary of messages per tenant domain ---" -ForegroundColor Cyan
    $filteredCsv = Import-Csv $filteredPath
    foreach ($domain in $tenantDomains) {
        $count = ($filteredCsv | Where-Object { $_.sender_address -match [regex]::Escape($domain) }).Count
        Write-Host "$domain : $count messages"
    }
    $grandTotal = $filteredCsv.Count
    Write-Host "`nGrand Total: $grandTotal messages" -ForegroundColor Green

    # ----------------------------
    # Prompt to disconnect from Exchange Online
    # ----------------------------
    $logout = Read-Host "`nWould you like to log out of Exchange Online? (Y/N)"
    if ($logout -in @('Y','y')) { Disconnect-ExchangeOnline -Confirm:$false; Write-Host "Disconnected from Exchange Online." }
}