Icewolf.EXO.SpamAnalyze.psm1

##############################################################################
# Invoke-SpamAnalyze.ps1
# Get SPAM Detail Info for Specific MessageTraceID in MessageTrace
# V2.0.0 04.05.2021 - Andres Bohren / Initial Version
# V2.0.1 07.05.2021 - Andres Bohren / Bugfixes
# V2.0.2 09.05.2021 - Andres Bohren / Bugfixes
# V2.0.3 16.06.2021 - Andres Bohren / Bugfixes Improvements
# V2.0.4 30.09.2021 - Andres Bohren / Bugfixes Improvements
# V2.0.5 23.12.2021 - Andres Bohren / Bugfixes Improvements
# V2.0.6 01.03.2022 - Andres Bohren / Added dependency Module ExchangeOnlineManagement
# V2.0.7-Alpha 10.03.2022 - Andres Bohren / Added Error Handling (Try / Catch) for DNS over HTTPS Querys
# - DNS Query changed from Cloudflare to Google
# https://developers.google.com/speed/public-dns/docs/doh
# https://developers.google.com/speed/public-dns/docs/doh/json
# - Subject is Limited to 20 Characters, so the MessageTraceId is still visible at long Subjects
# V2.0.7-Beta
# - Added diffrent Error Handling (Try / Catch) for DNS over HTTPS Querys
# V2.0.7-Gamma
# - Added Info for Message Event "Send External"
# - Fixed Issue with DNS over HTTPS Querys
##############################################################################
#Requires -Modules ExchangeOnlineManagement

Function Invoke-SpamAnalyze
{

<#
.SYNOPSIS
         
.DESCRIPTION
    Get SPAM Detail Info for Specific MessageTraceID in MessageTrace
 
.PARAMETER Recipientaddress
    The Emailaddress of the Recipient
 
.PARAMETER SenderAddress
    The Emailadress of the Sender
 
    .EXAMPLE
.\SpamAnalyze.ps1 -SenderAddress SenderAddress@domain.tld -RecipientAddress RecipientAddress@domain.tld
 
.LINK
#>


Param(
    [parameter(Mandatory=$true)][String]$RecipientAddress,
    [parameter(Mandatory=$true)][String]$SenderAddress
    )
    
Begin {
    ##############################################################################
    # Connect to Exchange Online
    ##############################################################################
    Function Connect-EXO {
            
        If ($Null -eq (Get-PsSession | Where-Object {$_.ComputerName -eq "outlook.office365.com"})) 
        {
            Write-Host "Connect to Exchange Online..." -f Gray
            Connect-ExchangeOnline -ShowBanner:$false

            If ($Null -eq (Get-PsSession | Where-Object {$_.ComputerName -like "*compliance.protection.outlook.com"}))
            {
                Write-Host "Connect to Security and Compliance..." -f Gray
                Connect-IPPSSession  -WarningAction Silentlycontinue    
            } else {
                Write-Host "Connection to Security and Compliance already exists" -ForegroundColor Green
            }

        }
        Else {
            Write-Host "Connection to Exchange Online already exists" -ForegroundColor Green

            If ($Null -eq (Get-PsSession | Where-Object {$_.ComputerName -like "*compliance.protection.outlook.com"}))
            {
                Write-Host "Connect to Security and Compliance..." -f Gray
                Connect-IPPSSession  -WarningAction Silentlycontinue    
            } else {
                Write-Host "Connection to Security and Compliance already exists" -ForegroundColor Green
            }

        }
    }

    ##############################################################################
    # Disconnect from Exchange Online
    ##############################################################################
    Function Disconnect-EXO 
    {
                Write-Host "Disconnect from Exchange Online" -f Gray
                #fWrite-Log -fLogtext "Disconnect from Exchange Online"
                Get-PSSession | Where-Object {($_.ComputerName -eq "outlook.office365.com") -AND ($_.ConfigurationName -eq "Microsoft.Exchange")} | Remove-PSSession
                Get-PSSession | Where-Object {($_.ComputerName -like "compliance.protection.outlook.com") -AND ($_.ConfigurationName -eq "Microsoft.Exchange")} | Remove-PSSession
                
    }
    

    ##############################################################################
    # Check MessageTraceDetail
    ##############################################################################
    Function Get-SPAMinfo {
        Param(
            [parameter(Mandatory=$false)][String]$RecipientAddress,
            [parameter(Mandatory=$false)][String]$SenderAddress,
            [parameter(Mandatory=$true)][String]$MessageTraceId
            )
        
        $Start = (Get-Date).AddDays(-10) 
        $End = (Get-Date)

        Write-Host "Message events:" -ForegroundColor Magenta
        $MTDetail = Get-MessageTraceDetail -MessageTraceId $MessageTraceId -RecipientAddress $RecipientAddress -SenderAddress $SenderAddress -StartDate $Start -EndDate $End | Sort-Object Date

        $MTEventFail = $MTDetail | Where-Object {$_.event -eq "Failed"}
        If ($Null -ne $MTEventFail) {
            Write-Host "Failed-Event: " -ForegroundColor Magenta
            Write-Host (" Action: " +$MTEventFail.action )
            Write-Host
        }        
        $MTEventMal = $MTDetail | Where-Object {$_.event -eq "Malware"}
        If ($Null -ne $MTEventMal) {
            Write-Host "Malware-Event: " -ForegroundColor Magenta
            Write-Host (" Action: " +$MTEventMal.action )
            Write-Host
        }
        
        #Send External / Extern senden
        $MTEventExternal = $MTDetail | Where-Object {$_.event -match "Extern"} 
        If ($Null -ne $MTEventExternal) 
        {
            Foreach ($SendExternal in $MTEventExternal)
            {
                Write-Host "Send External: " -ForegroundColor Magenta
                [xml]$xmlE =$MTEventExternal.Data
                #$xmlE.root.MEP
            
                $Items = $xmle.root.MEP.Count -1
                for ($i=0; $i -lt$Items; $i++)
                {
                    $Item = $xmle.root.MEP.Item($i)
                    #$Item = $xmle.root.MEP.Item(1)
                    $ItemProperty = ($item | Get-Member | Where-Object {$_.MemberType -eq "Property" -and $_.Name -ne "Name"} | Select Name).Name
                    $ItemName = $Item.Name
                    $ItemValue = $Item.$ItemProperty
                    If ($ItemName -eq "CustomData")
                    {
                        $Blob = $ItemValue.Split(";")
                        foreach ($Line in $blob)
                        {
                            Write-Host " $Line"
                        }                        
                    } else {
                        Write-Host " $ItemName : $ItemValue"
                    }
                    
                }
            }
        }

        $MTEventSPM = $MTDetail | Where-Object {$_.event -eq "Spam"} | Select-Object -uniq
        # SPAM Detail
        If ($Null -ne $MTEventSPM) {
            Write-Host "SPAM-Event: " -ForegroundColor Magenta
            Write-Host (" Action: " +$MTEventSPM.action )
            Write-Host
            Write-Host "SPAM-Event Details:" -ForegroundColor Magenta
            Write-Host "Anti-spam message headers: https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/anti-spam-message-headers" -f Cyan
            Write-Host "Spam confidence levels: https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/spam-confidence-levels" -f Cyan
            [xml]$xmlS = $MTEventSPM.Data
            $RcptCount  = ($xmlS.root.MEP | Where-Object {$_.Name -eq "RcptCount"})
            $DI         = ($xmlS.root.MEP | Where-Object {$_.Name -eq "DI"})
            $SCL        = ($xmlS.root.MEP | Where-Object {$_.Name -eq "SCL"})
            $Score        = ($xmlS.root.MEP | Where-Object {$_.Name -eq "Score"})
            $SFV        = ($xmlS.root.MEP | Where-Object {$_.Name -eq "SFV"})
            $ClientIP   = ($xmlS.root.MEP | Where-Object {$_.Name -eq "CIP"})
            $Country    = ($xmlS.root.MEP | Where-Object {$_.Name -eq "Ctry"})
            $HeloString = ($xmlS.root.MEP | Where-Object {$_.Name -eq "H"})
            $ReturnPath = ($xmlS.root.MEP | Where-Object {$_.Name -eq "ReturnPath"})
            $Language   = ($xmlS.root.MEP | Where-Object {$_.Name -eq "Language"})
            Write-Host (" RecipientCount: " +$RcptCount.Integer)
            switch ($DI.String) {
                "SB" { $DI = "(SB) The sender of the message was blocked" }
                "SQ" { $DI = "(SQ) The message was quarantined" }
                "SD" { $DI = "(SD) The message was deleted" }
                "SJ" { $DI = "(SJ) The message was sent to the recipient's Junk Email folder" }
                "SN" { $DI = "(SN) The message was routed through the higher risk delivery pool" }
                "SO" { $DI = "(SO) The message was routed through the normal outbound delivery pool" }
            }
            Write-Host (" DI: " +$DI)
            # Color for SCL
            switch ($SCL.Integer) {
                -1 { $cSCL = "Green"; $Folder = "Inbox" }
                0 { $cSCL = "Green"; $Folder = "Inbox" }
                1 { $cSCL = "Green"; $Folder = "Inbox" }
                2 { $cSCL = "Green"; $Folder = "Inbox" }
                3 { $cSCL = "Green"; $Folder = "Inbox" }
                4 { $cSCL = "Green"; $Folder = "Inbox" }
                5 { $cSCL = "Yellow"; $Folder = "Junk-E-Mail" }
                6 { $cSCL = "Yellow"; $Folder = "Junk-E-Mail" }
                7 { $cSCL = "Red"; $Folder = "Quarantaine" }
                8 { $cSCL = "Red"; $Folder = "Quarantaine" }
                9 { $cSCL = "Red"; $Folder = "Quarantaine" }
            }
            Write-Host (" SpamConfidenceLevel (SCL): "+$SCL.Integer +" Deliver to: " +$Folder +")") -f $cSCL
            Write-Host (" SpamScoreLevel (Score): "+$Score.Integer )
            switch ($SFV.String) 
            {
                "BLK" { $SFV = "(BLK) Filtering was skipped and the message was blocked because it originated from a blocked sender" }
                "NSPM" { $SFV = "(NSPM) The message was marked as non-spam and was sent to the intended recipients" }
                "SFE" { $SFV = "(SFE) Filtering was skipped and the message was allowed because it was sent from an address in a user's Safe Senders list"}
                "SKA" { $SFV = "(SKA) The message skipped spam filtering and was delivered to the Inbox because the sender was in the allowed senders list or allowed domains list in an anti-spam policy"}
                "SKB" { $SFV = "(SKB) The message skipped spam filtering and was delivered to the Inbox because the sender was in the allowed senders list or allowed domains list in an anti-spam policy"}
                "SKI" { $SFV = "(SKI) Similar to SFV:SKN, the message skipped spam filtering for another reason (for example, an intra-organizational email within a tenant)"}
                "SKN" { $SFV = "(SKN) The message was marked as non-spam prior to being processed by spam filtering. For example, the message was marked as SCL -1 or Bypass spam filtering by a mail flow rule"}
                "SKQ" { $SFV = "(SKQ) The message was released from the quarantine and was sent to the intended recipients"}
                "SKS" { $SFV = "(SKS) The message was marked as spam prior to being processed by the content filter" }
                "SPM" { $SFV = "(SPM) The message was marked as spam by spam filtering" }
                            
            }
            Write-Host (" SpamFilterVerdikt (SFV): " +$SFV)
            Write-Host (" SenderClientIP (CIP): " +$ClientIP.String)
            Write-Host (" Country (CTRY): " +$Country.String)
            Write-Host (" HeloString (H): " +$HeloString.String)
            Write-Host (" ReturnPath: " +$ReturnPath.String)
            Write-Host (" Language: " +$Language.String)
            Write-Host    

        } Else {
            Write-Host "SPAM-Event: " -ForegroundColor Magenta
            Write-Host (" INFO: This mail contains no 'Spam' event ") -f Cyan
            Write-Host
        }
    }
}

Process {
    #Set Window and Bufferzite
    $pshost = get-host
    $pswindow = $pshost.ui.rawui
    $LanguageMode = $ExecutionContext.SessionState.LanguageMode
    If ($LanguageMode -eq "Fulllanguage"){
        if ($pswindow.WindowSize.Width -lt 220){
            $newsize = $pswindow.buffersize
            $newsize.height = 8000
            $newsize.width = 220
            $pswindow.buffersize = $newsize
            $newsize = $pswindow.windowsize
            $newsize.width = 180
            $newsize.height = 60
            $pswindow.windowsize = $newsize
        }
    }

    #Call Function to Connect to Exchange Online
    Connect-EXO

    #Check if Messagetrace is available
    Try {
        Get-Command Get-MessageTrace -ErrorAction Stop | Out-Null
    } catch { 
        Write-Host "No Permission for the Command: Get-MessageTrace. Stopping script."
        #exit
        Break
    }

    #Set Start- and Enddate for Messagetrace
    $Start = ((Get-Date).AddDays(-10))
    $End = Get-Date
    
    #Messagetrace depending on Parameters
    If ($SenderAddress -ne $Null)
    {
        If ($RecipientAddress -ne $Null)
        {
            $MT = Get-MessageTrace -StartDate (get-date).AddDays(-10) -EndDate (get-date) -SenderAddress $SenderAddress -RecipientAddress $RecipientAddress 
        } else {
            $MT = Get-MessageTrace -StartDate (get-date).AddDays(-10) -EndDate (get-date) -SenderAddress $SenderAddress 
        }
    } else {
        #SenderAddress = $Null / RecipientAddress populated
        $MT = Get-MessageTrace -StartDate (get-date).AddDays(-10) -EndDate (get-date) -RecipientAddress $RecipientAddress  
    }
    #$MT | Format-Table Received, SenderAddress, RecipientAddress, Subject, Status, MessageTraceID
    $MT | Select-Object Received, SenderAddress, RecipientAddress, @{label='Subject';expression={$_.Subject.Substring(0,20)}}, Status, MessageTraceID  | Format-Table

    If ($Null -eq $MT)
    {
        Write-Host "No Results in Message Trace found"
    } else {


        #Input MessageTraceID
        $readhost = Read-Host "MessageTraceID?"
        If ($readhost -eq "")
        {
            Write-Host "Not a MessageTraceID... Stopping Script"
        } else {
            #Write-Host "DEBUG: Readhost: $readhost"
            Foreach ($Line in $MT)
            {
                If ($readhost -eq $Line.MessageTraceId)
                {
                    
                    #Write-Host "DEBUG: MessageTraceID: $($Line.MessageTraceId)"
                    #Write-Host "DEBUG: Sender: $($Line.Senderaddress)"
                    #Write-Host "DEBUG: Recipient: $($Line.RecipientAddress)"
                    $MessageTraceId = $Line.MessageTraceId
                    $MTSenderAddress = $Line.Senderaddress
                    $MTRecipientAddress = $Line.RecipientAddress
                    $MTStatus = $Line.Status
                    $MTSubject = $Line.Subject
                    $MTReceived = $Line.Received
                    $MTMessageID = $Line.MessageID

                    #Infos from Message Trace
                    Write-Host
                    Write-Host "E-Mail Detail:" -ForegroundColor Magenta
                    Write-Host " Message ID: $MTMessageID"
                    Write-Host " Received: $MTReceived"
                    Write-Host " Sender: $MTSenderAddress"
                    Write-Host " Recipient: $MTRecipientAddress"
                    Write-Host " Subject: $MTSubject"
                    Write-Host " Status: $MTStatus"
                    Write-Host

                    #Check Recipient
                    $ExoRecipient = Get-Recipient -Identity $MTRecipientAddress
                    #$ExoRecipient
                    $RecipientTypeDetails = $ExoRecipient.RecipientTypeDetails

                    Write-Host "Recipient Details" -ForegroundColor Magenta
                    Write-Host " RecipientTypeDetails: $RecipientTypeDetails"
                    Write-Host

                    #JunkMailConfiguration of Mailbox
                    $SenderDomain = ($SenderAddress.Split("@")[1])
                    If ($RecipientTypeDetails -like "*Mailbox")
                    {
                        $JMC = Get-MailboxJunkEmailConfiguration -Identity $MTRecipientAddress
                        If ($NULL -ne $JMC)
                        {
                            Write-Host "Recipient JunkMailConfiguration" -ForegroundColor Magenta
                            Write-Host " TrustedListsOnly: $($JMC.TrustedListsOnly)"
                            Write-Host " ContactsTrusted: $($JMC.ContactsTrusted)"
                            Write-Host

                            Write-Host "Check if $MTSenderAddress exists in MAILBOX ($MTRecipientAddress) Trusted-/BlockedSenders list: " -ForegroundColor Magenta        
                            If ($JMC.TrustedSendersAndDomains -contains $MTSenderAddress)
                            {
                                Write-Host " USER Junk-E-Mail Config: Found in 'TrustedSendersAndDomains'" -f Green
                            } Else {
                                Write-Host " USER Junk-E-Mail Config: Not found in 'TrustedSendersAndDomains'" -f White
                            }
                            
                            If ($JMC.BlockedSendersAndDomains -contains $MTSenderAddress)
                            {
                                Write-Host " USER Junk-E-Mail Config: Found in 'BlockedSendersAndDomains'" -f Red
                            } Else {
                                Write-Host " USER Junk-E-Mail Config: Not found in 'BlockedSendersAndDomains'" -f White
                            }
                            Write-Host
                            
                            Write-Host "Check if $SenderDomain exists in MAILBOX ($MTRecipientAddress) Trusted-/BlockedSenders list: " -ForegroundColor Magenta    
                            If ($JMC.TrustedSendersAndDomains -contains $SenderDomain)
                            {
                                Write-Host " USER Junk-E-Mail Config: Found in 'TrustedSendersAndDomains'" -f Green
                            } Else {
                                Write-Host " USER Junk-E-Mail Config: Not found in 'TrustedSendersAndDomains'" -f White
                            }
                            
                            If ($JMC.BlockedSendersAndDomains -contains $SenderDomain)
                            {
                                Write-Host " USER Junk-E-Mail Config: Found in 'BlockedSendersAndDomains'" -f Red
                            } Else {
                                Write-Host " USER Junk-E-Mail Config: Not found in 'BlockedSendersAndDomains'" -f White
                            }
                            Write-Host

                        }                    
                    }

                    #GLOBALConfig
                    Write-Host "Check if $MTSenderAddress exists in GLOBAL Trusted-/BlockedSender list: " -ForegroundColor Magenta
                    $GLOBALJunkConfig = Get-HostedContentFilterPolicy
                    #Allowed Senders
                    If ($GLOBALJunkConfig.AllowedSenders -match $MTSenderAddress)
                    {
                        Write-Host " GLOBAL EAC SpamFilter: Found in 'AllowedSenders'" -f Green
                    } Else {
                        Write-Host " GLOBAL EAC SpamFilter: Not found in 'AllowedSenders'" -f White
                    }

                    #Blocked Senders
                    If ($GLOBALJunkConfig.BlockedSenders -match $MTSenderAddress)
                    {
                        Write-Host " GLOBAL EAC SpamFilter: Found in 'BlockedSenders'" -f Red
                    } Else {
                        Write-Host " GLOBAL EAC SpamFilter: Not found in 'BlockedSenders'" -f White
                    }
                    Write-Host

                    Write-Host "Check if $SenderDomain exists in GLOBAL Allowed-/BlockedSenderDomain list: " -ForegroundColor Magenta
                    #Allowed Domains
                    If ($GLOBALJunkConfig.AllowedSenderDomains.Domain -contains $SenderDomain)
                    {
                        Write-Host " GLOBAL EAC SpamFilter: Found in 'AllowedSenderDomains'" -f Green
                    } Else {
                        Write-Host " GLOBAL EAC SpamFilter: Not found in 'AllowedSenderDomains'" -f White
                    }

                    #Allowed Senders
                    If ($GLOBALJunkConfig.BlockedSenderDomains.Domain -contains $SenderDomain)
                    {
                        Write-Host " GLOBAL EAC SpamFilter: Found in 'BlockedSenderDomains'" -f Red
                    } Else {
                        Write-Host " GLOBAL EAC SpamFilter: Not found in 'BlockedSenderDomains'" -f White
                    }
                    Write-Host


                    Get-SPAMinfo -RecipientAddress $MTRecipientAddress -SenderAddress $MTSenderAddress -MessageTraceId $MessageTraceId

                    #DNS Records
                    Write-Host "DNS Records of $SenderDomain" -ForegroundColor Magenta


                    #Test DNS over HTTP
                    $json = Invoke-RestMethod -URI "https://dns.google/resolve?name=google.com&type=NS"
                    If (($json | Get-Member -MemberType NoteProperty).count -lt 3)
                    {
                        Write-Host "This System does not Support DNS over HTTPS" -ForegroundColor Yellow
                    } else {

                        #NS
                        Write-Host "NS" -ForegroundColor Magenta
                        try {
                            $json = Invoke-RestMethod -URI "https://dns.google/resolve?name=$SenderDomain&type=NS"
                            [string]$NS = $json.Answer.data
                            $NS
                        } catch {
                            if($_.ErrorDetails.Message) {
                                Write-Host $_.ErrorDetails.Message
                            } else {
                                Write-Host $_
                            }
                        }

                        #MX
                        Write-Host "MX" -ForegroundColor Magenta
                        try {
                            $json = Invoke-RestMethod -URI "https://dns.google/resolve?name=$SenderDomain&type=MX"
                            [string]$MX = $json.Answer.data
                            $MX
                        } catch {
                            if($_.ErrorDetails.Message) {
                                Write-Host $_.ErrorDetails.Message
                            } else {
                                Write-Host $_
                            }
                        }

                        #SPF
                        Write-Host "SPF" -ForegroundColor Magenta
                        try {
                            $json = Invoke-RestMethod -URI "https://dns.google/resolve?name=$SenderDomain&type=TXT"
                            $TXT = $json.Answer.data
                            $TXT = $TXT | Where-Object {$_ -match "v=spf1"}
                            $SPF = $TXT
                            If ($Null -eq $SPF)
                            {
                                Write-Host "NO SPF Record found" -ForegroundColor Yellow
                            } else {
                                $SPF
                            }
                        } catch {
                            if($_.ErrorDetails.Message) {
                                Write-Host $_.ErrorDetails.Message
                            } else {
                                Write-Host $_
                            }
                        }

                        #DKIM
                        Write-Host "DKIM (Only checking for: selector1/selector2)" -ForegroundColor Magenta
                        try {
                            $json = Invoke-RestMethod -URI "https://dns.google/resolve?name=Selector1._domainkey.$SenderDomain&type=CNAME"
                            $DKIM1 = $json.Answer.data
                            $json = Invoke-RestMethod -URI "https://dns.google/resolve?name=Selector2._domainkey.$SenderDomain&type=CNAME"
                            $DKIM2 = $json.Answer.data
                            [string]$DKIM = "$DKIM1 $DKIM2"                    
                            If ($DKIM -eq " ")
                            {
                                Write-Host "NO DKIM Record found" -ForegroundColor Yellow
                            } else {
                                $DKIM
                            }
                        } catch {
                            if($_.ErrorDetails.Message) {
                                Write-Host $_.ErrorDetails.Message
                            } else {
                                Write-Host $_
                            }
                        }

                        #DMARC
                        Write-Host "DMARC" -ForegroundColor Magenta
                        try {
                            $json = Invoke-RestMethod -URI "https://dns.google/resolve?name=_dmarc.$SenderDomain&type=TXT"
                            $DMARC = $json.Answer.data
                            If ($Null -eq $DMARC)
                            {
                                Write-Host "NO DMARC Record found" -ForegroundColor Yellow
                            } else {
                                $DMARC
                            }
                        } catch {
                            if($_.ErrorDetails.Message) {
                                Write-Host $_.ErrorDetails.Message
                            } else {
                                Write-Host $_
                            }
                        }
                    }
                }
            }
        }
    }
}

End {
    #Disconnect from Exchange Online and
    #Disconnect-EXO
}
}