Scripts/Get-MessageTraceLog.ps1

# This contains a function to collect the Message Trace logging.
Function StartDateMTL {
    param([switch]$Quiet)

    if (($startDate -eq "") -Or ($null -eq $startDate))
    {
        $script:StartDate = [datetime]::Now.ToUniversalTime().AddDays(-90)
        if (-not $Quiet) {
            write-LogFile -Message "[INFO] No start date provided by user setting the start date to: $($script:StartDate.ToString("yyyy-MM-ddTHH:mm:ssK"))" -Color "Yellow"
        }
    }
    else {
        $script:StartDate = $startDate -as [datetime]
        if (!$startDate -and -not $Quiet) {
            write-LogFile -Message "[WARNING] Not A valid start date and time, make sure to use YYYY-MM-DD" -Color "Red"
        } 
    }
}

function EndDateMTL {
    param([switch]$Quiet)

    if (($endDate -eq "") -Or ($null -eq $endDate))
    {
        $script:EndDate = [datetime]::Now.ToUniversalTime()
        if (-not $Quiet) {
            write-LogFile -Message "[INFO] No end date provided by user setting the end date to: $($script:EndDate.ToString("yyyy-MM-ddTHH:mm:ssK"))" -Color "Yellow"
        }
    }
    else {
        $script:EndDate = $endDate -as [datetime]
        if (!$endDate -and -not $Quiet) {
            write-LogFile -Message "[WARNING] Not A valid end date and time, make sure to use YYYY-MM-DD" -Color "Red"} 
    }
}

function Get-MessageTraceLog
{
<#
    .SYNOPSIS
    Collects the trace messages as they pass through the cloud-based organization.
 
    .DESCRIPTION
    Collects the trace messages as they pass through the cloud-based organization.
    Only 10 days of history is available. Output is saved in: Output\MessageTrace\
     
    .PARAMETER UserIds
    UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions.
 
    .PARAMETER StartDate
    startDate is the parameter specifying the start date of the date range.
    Default: Today 10 days
 
    .PARAMETER EndDate
    endDate is the parameter specifying the end date of the date range.
    Default: Now
 
    .PARAMETER OutputDir
    outputDir is the parameter specifying the output directory.
    Default: Output\MessageTrace
 
    .PARAMETER Encoding
    Encoding is the parameter specifying the encoding of the CSV output file.
    Default: UTF8
 
    .PARAMETER LogLevel
    Specifies the level of logging:
    None: No logging
    Minimal: Critical errors only
    Standard: Normal operational logging
    Debug: Verbose logging for debugging purposes
    Default: Standard
 
    .EXAMPLE
    Get-MessageTraceLog
    Collects the trace messages for all users.
     
    .EXAMPLE
    Get-MessageTraceLog -UserIds HR@invictus-ir.com
    Collects the trace messages for the user HR@invictus-ir.com.
 
    .EXAMPLE
    Get-MessageTraceLog -UserIds "Test@invictus-ir.com,HR@invictus-ir.com"
    Collects the trace messages for the users Test@invictus-ir.com and HR@invictus-ir.com.
 
    .EXAMPLE
    Get-MessageTraceLog -UserIds "*@invictus-ir.com"
    Collects the trace messages for the full @invictus-ir.com domain.
 
    .EXAMPLE
    Get-MessageTraceLog -UserIds Test@invictus-ir.com -StartDate 1/4/2023 -EndDate 5/4/2023
    Gets the trace messages for the user Test@invictus-ir.com between 1/4/2023 and 5/4/2023.
#>

    [CmdletBinding()]
    param(
        [string[]]$UserIds,
        [string]$StartDate,
        [string]$EndDate,
        [string]$OutputDir = "Output\MessageTrace",
        [string]$Encoding = "UTF8",
        [ValidateSet('None', 'Minimal', 'Standard', 'Debug')]
        [string]$LogLevel = 'Standard'
    )
    
    Write-LogFile -Message "=== Starting Message Trace Log Collection ===" -Color "Cyan" -Level Standard

    StartDateMTL -Quiet
    EndDateMTL -Quiet
    
    Set-LogLevel -Level ([LogLevel]::$LogLevel)
    $isDebugEnabled = $script:LogLevel -eq [LogLevel]::Debug

    $date = Get-Date -Format "yyyyMMddHHmm"
    $summary = @{
        StartTime = Get-Date
        ProcessingTime = $null
    }

    if ($isDebugEnabled) {
        Write-LogFile -Message "[DEBUG] PowerShell Version: $($PSVersionTable.PSVersion)" -Level Debug
        Write-LogFile -Message "[DEBUG] Input parameters:" -Level Debug
        Write-LogFile -Message "[DEBUG] UserIds: '$UserIds'" -Level Debug
        Write-LogFile -Message "[DEBUG] StartDate input: '$StartDate'" -Level Debug
        Write-LogFile -Message "[DEBUG] EndDate input: '$EndDate'" -Level Debug
        Write-LogFile -Message "[DEBUG] OutputDir: '$OutputDir'" -Level Debug
        Write-LogFile -Message "[DEBUG] Encoding: '$Encoding'" -Level Debug
        Write-LogFile -Message "[DEBUG] LogLevel: '$LogLevel'" -Level Debug
        
        $exchangeModule = Get-Module -Name ExchangeOnlineManagement -ErrorAction SilentlyContinue
        if ($exchangeModule) {
            Write-LogFile -Message "[DEBUG] ExchangeOnlineManagement Module Version: $($exchangeModule.Version)" -Level Debug
        } else {
            Write-LogFile -Message "[DEBUG] ExchangeOnlineManagement Module not loaded" -Level Debug
        }
    }

    if ($isDebugEnabled) {
        Write-LogFile -Message "[DEBUG] Date processing complete:" -Level Debug
        Write-LogFile -Message "[DEBUG] Processed StartDate: $($script:StartDate)" -Level Debug
        Write-LogFile -Message "[DEBUG] Processed EndDate: $($script:EndDate)" -Level Debug
        Write-LogFile -Message "[DEBUG] Date range span: $(($script:EndDate - $script:StartDate).TotalDays) days" -Level Debug
    }    

    if (!(test-path $OutputDir)) {
        New-Item -ItemType Directory -Force -Path $outputDir > $null
    } else {
        if (!(Test-Path -Path $OutputDir)) {
            Write-Error "[Error] Custom directory invalid: $OutputDir exiting script" -ErrorAction Stop
            Write-LogFile -Message "[Error] Custom directory invalid: $OutputDir" -Level Minimal
        }
    }

    if (($null -eq $UserIds) -Or ($UserIds -eq "")) {
        Write-LogFile -Message "[INFO] No users provided. Getting the Message Trace Log for all users between $($script:StartDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($script:EndDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK"))" -Color "Yellow" -Level Standard
        Retrieve-MessageTrace -StartDate $script:StartDate -endDate $script:EndDate -OutputFile "$OutputDir\$($date)-AllUsers-MTL.csv"
    } else {
        if($UserIds -match "\*"){
            Write-LogFile -Message "[INFO] An entire domain has been provided, retrieving all messages between $($script:StartDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($script:EndDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK"))" -Color "Yellow" -Level Standard
        }
        $users = $UserIds.Split(",")

        $users | foreach {
            $user = $_

            if ($isDebugEnabled) {
                Write-LogFile -Message "[DEBUG] Processing user: '$user'" -Level Debug
                Write-LogFile -Message "[DEBUG] Output file path: '$outputFile'" -Level Debug
            }

            write-logFile -Message "[INFO] Collecting the Message Trace Log for $user between $($script:StartDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($script:EndDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK"))" -Level Standard
            $outputFile = "$OutputDir\$($user.Replace('*@',''))-MTL.csv"
            Remove-Item $outputFile -ErrorAction SilentlyContinue

            Retrieve-MessageTrace -StartDate $script:StartDate -endDate $script:EndDate -OutputFile $outputFile -searchParams @{"RecipientAddress" = $user}
            Retrieve-MessageTrace -StartDate $script:StartDate -endDate $script:EndDate -OutputFile $outputFile -searchParams @{"SenderAddress" = $user}

            if (test-path $outputFile) {
                Write-LogFile -Message "[INFO] Output is written to: $outputFile" -Color "Green" -Level Standard
            } else {
                Write-LogFile -Message "[INFO] No message Trace logging found for $($user)" -Color "Yellow" -Level Standard
            }
        }
    }

    $summary.ProcessingTime = (Get-Date) - $summary.StartTime
    Write-LogFile -Message "`n=== Message Trace Analysis Summary ===" -Color "Cyan" -Level Standard
    Write-LogFile -Message "Analysis Period: $($script:StartDate) to $($script:EndDate)" -Level Standard
    Write-LogFile -Message "Output Statistics:" -Level Standard
    Write-LogFile -Message " Output Directory: $OutputDir" -Level Standard
    Write-LogFile -Message " Processing Time: $($summary.ProcessingTime.ToString('mm\:ss'))" -Color "Green" -Level Standard
    Write-LogFile -Message "===================================" -Color "Cyan" -Level Standard
}

function Retrieve-MessageTrace {
# Handle the pagination of the MessageTraceV2 API
    param(
        [DateTime]$startDate,
        [DateTime]$endDate,
        $searchParams = @{},
        [string]$OutputFile
    )

    if ($isDebugEnabled) {
        Write-LogFile -Message "[DEBUG] Retrieve-MessageTrace function called" -Level Debug
        Write-LogFile -Message "[DEBUG] StartDate: $startDate" -Level Debug
        Write-LogFile -Message "[DEBUG] EndDate: $endDate" -Level Debug
        Write-LogFile -Message "[DEBUG] OutputFile: '$OutputFile'" -Level Debug
        Write-LogFile -Message "[DEBUG] SearchParams: $($searchParams | ConvertTo-Json -Compress)" -Level Debug
    }

    $localSummary = @{
        MessageCount = 0
        SentCount = 0
        ReceivedCount = 0
        StatusCounts = @{}
    }

    $currentEnd = $endDate
    while($currentEnd -gt $startDate){
        $currentStart = $currentEnd.addDays(-10)
        if($currentStart -lt $StartDate) {
            $currentStart = $StartDate
        }

        if ($isDebugEnabled) {
            Write-LogFile -Message "[DEBUG] Processing date chunk:" -Level Debug
            Write-LogFile -Message "[DEBUG] Current start: $currentStart" -Level Debug
            Write-LogFile -Message "[DEBUG] Current end: $currentEnd" -Level Debug
            Write-LogFile -Message "[DEBUG] Chunk span: $(($currentEnd - $currentStart).TotalDays) days" -Level Debug
        }

        $searchParams.ResultSize = 5000
        $searchParams.StartDate = $currentStart
        $searchParams.EndDate = $currentEnd
        $resultCount = 5000
        
        while($resultCount -ge 5000) {
            $results = Get-MessageTraceV2 @searchParams
            $resultCount = $results.Count

            if($results){
                $results | Export-Csv $outputFile -ErrorAction SilentlyContinue -NoTypeInformation -Encoding $Encoding -Append
                Write-LogFile -Message "[INFO] Found $resultCount records between $($results[-1].Received) and $($results[0].Received)"  -Level Standard

                $searchParams.EndDate = $results[-1].Received.ToString("O")
                $searchParams.StartingRecipientAddress = $results[-1].RecipientAddress
            }
        }

        $currentEnd=$currentStart
    }
}