Public/Exchange/MessageTrace/Get-MessageTraceInfo.ps1

<#
    .SYNOPSIS
    Retrieves message trace information based on specified criteria.
 
    .DESCRIPTION
    This function retrieves message trace information from Exchange Online based on the specified criteria. It supports filtering by sender address, recipient address, start date, end date, connector name, transport rule name, and using connector flag.
 
    .PARAMETER SenderAddress
    Specifies the sender address to filter the message trace. This parameter accepts a string or an array of strings. Wildcards (*) can be used to match multiple sender addresses.
 
    .PARAMETER RecipientAddress
    Specifies the recipient address to filter the message trace. This parameter accepts a string or an array of strings. Wildcards (*) can be used to match multiple recipient addresses.
 
    .PARAMETER StartDate
    Specifies the start date to filter the message trace. This parameter accepts a DateTime object.
 
    .PARAMETER EndDate
    Specifies the end date to filter the message trace. This parameter accepts a DateTime object.
 
    .PARAMETER ByConnectorName
    Specifies the connector name to filter the message trace. This parameter accepts a string.
 
    .PARAMETER ByTransportRuleName
    Specifies the transport rule name to filter the message trace. This parameter accepts a string.
 
    .PARAMETER UsingConnector
    Specifies whether to filter the message trace using a connector. This parameter is a switch parameter.
 
    .EXAMPLE
    Get-MessageTraceInfo -SenderAddress "john.doe@example.com" -RecipientAddress "jane.doe@example.com" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) -ByConnectorName "OutboundConnector" -UsingConnector
    Retrieves message trace information for messages sent by "john.doe@example.com" to "jane.doe@example.com" within the last 7 days, using the "OutboundConnector" connector.
 
    .EXAMPLE
    Get-MessageTraceInfo -RecipientAddress "jane.doe@example.com" -StartDate (Get-Date).AddDays(-30) -EndDate (Get-Date) -ByTransportRuleName "Confidential" -UsingConnector
    Retrieves message trace information for messages received by "jane.doe@example.com" within the last 30 days, filtered by the "Confidential" transport rule, using a connector.
 
    .NOTES
    This function requires the Exchange Online PowerShell module to be installed and connected to an Exchange Online organization.
 
    For some event, it will fail to get messagetracedetail
#>


function Get-MessageTraceInfo {
    param(
        [Parameter(Mandatory = $false)]
        [String[]] $SenderAddress,
        [Parameter(Mandatory = $false)]
        [String[]] $RecipientAddress,
        [Parameter(Mandatory = $false)]
        [datetime] $StartDate,
        [Parameter(Mandatory = $false)]
        [datetime] $EndDate,
        [Parameter(Mandatory = $false)]
        [String] $ByConnectorName,
        [Parameter(Mandatory = $false)]
        [String]$ByTransportRuleName,
        [Parameter(Mandatory = $false)]
        [Switch] $UsingConnector
    )

    [System.Collections.Generic.List[PSObject]]$messagesInfo = @()
    [System.Collections.Generic.List[PSObject]]$messagesList = @()

    # PageSize maximum default = 1000; Max PageSize = 5000
    # There isn't really a reason to decrease this in this instance.
    <#
    not present with Get-MessageTraceV2
    $messageTraceParams = @{
        PageSize = 5000
    }
    #>


    if ($RecipientAddress -and $SenderAddress) {
        $messageTraceParams = @{
            RecipientAddress = $RecipientAddress
            SenderAddress    = $SenderAddress
            #Status = 'Delivered'
        }
    }
    elseif ($RecipientAddress -and -not $SenderAddress) {
        $messageTraceParams = @{
            RecipientAddress = $RecipientAddress
        }
    }
    elseif ($SenderAddress -and -not $RecipientAddress) {
        $messageTraceParams = @{
            SenderAddress = $SenderAddress
        }
    }

    # convert to UTC because Exchange Online used UTC time
    if ($StartDate) {
        $messageTraceParams.Add('StartDate', $StartDate.ToUniversalTime())
        
        if ($EndDate) {
            $messageTraceParams.Add('EndDate', $EndDate.ToUniversalTime())
        }
        else {
            $messageTraceParams.Add('EndDate', $((Get-Date).ToUniversalTime()))
        }
    }

    <#
    Not present with Get-MessageTraceV2
    $messageTraceParams.Add('Page', 1)
    $maxPage = 1000
     
     
    $page = 1
    $maxPage = 1000
    $pageSize = 5000 # Max pagesize is 5000. There isn't really a reason to decrease this in this instance.
    #>


    $messages = Get-MessageTraceV2 @messageTraceParams | Select-Object MessageId, Received, SenderAddress, RecipientAddress, Subject, Status, ToIP, FromIP, Size, MessageTraceId, StartDate, EndDate
    
    foreach ($message in $messages) {
        $messagesList.Add($message)
    }

    Write-Host -ForegroundColor Cyan "Found $($messagesList.Count) messages (in total, without the filter)"
    
    $oldProgressPreference = $ProgressPreference

    $ProgressPreference = 'SilentlyContinue'

    foreach ($message in $messagesList) {
        # Ignore Journaling events if exist
        
        if ($UsingConnector.IsPresent) {
            $traceDetail = $message | Get-MessageTraceDetailV2 | Where-Object { $_.Action -eq 'RouteMessageUsingConnector' -and $_.Event -ne 'Journal' }
        }
        elseif ($ByTransportRuleName) {
            $traceDetail = $message | Get-MessageTraceDetailV2 | Where-Object { $_.Detail -like "*$ByTransportRuleName*" }
        }
        elseif ($ByConnectorName) {
            #$traceDetail = $message | Get-MessageTraceDetailV2 -Event RECEIVE | Where-Object { $_.Data -like "*S:InboundConnectorData=Name=$ByConnectorName*" }
            $traceDetail = $message | Get-MessageTraceDetailV2 | Where-Object { $_.Data -like "*S:Microsoft.Exchange.Hygiene.TenantOutboundConnectorCustomData=Name=$ByConnectorName*" }
        }
        else {
            #$traceDetail = $message | Get-MessageTraceDetailV2 -Event RECEIVE | Where-Object { $_.Event -ne 'Journal' }
            # Event: -Event RECEIVE, SEND, FAIL, DELIVER, EXPAND, TRANSFER, DEFER, DROP
            # event list: https://learn.microsoft.com/en-us/exchange/mail-flow/transport-logs/message-tracking?view=exchserver-2019#event-types-in-the-message-tracking-log
            
            if ($message.Count -eq 1) {
                $traceDetail = Get-MessageTraceDetailV2 -MessageId $message.MessageId -MessageTraceId $message.MessageTraceId -RecipientAddress $message.RecipientAddress
            }
            else {
                # we found the last event only, don't know if it's the best way to do it
                $traceDetail = ($message | Get-MessageTraceDetailV2 | Where-Object { $_.Event -ne 'Journal' })[-1]
            }
        }

        # redirect has no messagetracedetails in event receive
        if ($null -ne $traceDetail) {

            $hashTable = @{}

            try {
                $XMLDoc = [XML]$traceDetail.Data
            }
            catch {
                Write-Host $_.Exception.Message
            }

            try {
                $MEPNodes = $XMLDoc.GetElementsByTagName('MEP')
            }
            catch {
                Write-Host $_.Exception.Message
            }

            for ($nodeval = 0; $nodeval -lt $MEPNodes.Count; $nodeval++) {
                $key = $MEPNodes[$nodeval].Attributes[0].Value.ToString()
                $value = $MEPNodes[$nodeval].Attributes[1].Value.ToString()

                $hashTable.Add($key, $value)
            }

            $object = [PSCustomObject] [ordered]@{
                SenderAddress        = $message.SenderAddress
                RecipientAddress     = $message.RecipientAddress
                ReturnPath           = $hashTable['ReturnPath']
                Subject              = $message.Subject
                Detail               = $traceDetail.detail
                Status               = $message.Status
                'Received(UTC)'      = $message.Received
                FromIP               = $message.FromIP
                ToIP                 = $message.ToIP
                ClientIP             = $hashTable['ClientIP']
                DeliveryPriority     = $hashTable['DeliveryPriority']
                # CustomData is parsed below
                #CustomData = $hashTable['CustomData']
                InboundConnectorData = ($hashtable['customdata'] -split ";'" -split ';' -match 'S:InboundConnectorData' -split 'S:InboundConnectorData=Name=')[1]
                ConnectorType        = ($hashtable['customdata'] -split ";'" -split ';' -match 'ConnectorType' -split 'ConnectorType=')[1]
                TLSVersion           = ($hashtable['customdata'] -split ";'" -split ';' -match 'S:tlsversion' -split 'S:tlsversion=')[1]
                TLSCipher            = ($hashtable['customdata'] -split ";'" -split ';' -match 'S:tlscipher' -split 'S:tlscipher=')[1]
                OriginOrg            = ($hashtable['customdata'] -split ";'" -split ';' -match 'S:Oorg' -split 'S:Oorg=')[1]
                MessageID            = $traceDetail.MessageId
                MessageTraceID       = $traceDetail.MessageTraceId
            }

            $messagesInfo.Add($object)
        }
    }

    $ProgressPreference = $oldProgressPreference
    
    return $messagesInfo
}