internal/functions/Expand-LogRecordSmtp.ps1
function global:Expand-LogRecordSmtp { <# .SYNOPSIS Expand the data from records group into a flat data record .DESCRIPTION Expand the data from records group into a flat data record .PARAMETER InputObject The dataset to expand .PARAMETER SessionIdName The name of the session grouping attribute .PARAMETER ShowProgress If specified, progress information on record processing is showed .EXAMPLE PS C:\> Expand-LogRecordSmtp -InputObject $DataSet Expand the data from records group into a flat data record #> [CmdletBinding()] param ( $InputObject, [string] $SessionIdName = "session-Id", [switch] $ShowProgress ) begin { $Error.Clear() } process { if ($ShowProgress) { $i = 0 if ($InputObject.count -lt 100) { $refreshInterval = 1 } else { $refreshInterval = [math]::Round($InputObject.count / 100) } } foreach ($record in $InputObject) { # assure qualified session in log $_startIndicator = $record.Group | Where-Object Event -eq "+" $_stopIndicator = $record.Group | Where-Object Event -eq "-" if ((-not $_startIndicator) -or (-not $_stopIndicator)) { Write-PSFMessage -Level Warning -Message "Detect fragmented record! Skip processing $($SessionIdName) '$($record.$SessionIdName)' in $($record.LogFolder)\$($record.LogFileName)" continue } # data text record in array, avoids parsing full data array $groupData = $record.Group.data # full text log $logtext = "" foreach ($item in $record.Group) { $logtext = $logtext + "$(if($logtext){"`n"})" + $item.event + " " + $item.data if ($item.context.Length -gt 0) { $logtext = $logtext + " (context: " + $item.context + ")" } } # ServerName $serverName = $record.Group[0].'connector-id'.split("\")[0] # ServerNameHELO [string]$_serverNameHELO = $groupData | Where-Object { $_ -like "220 * Microsoft*" } | Select-Object -First 1 if ($_serverNameHELO) { [String]$serverNameHELO = $_serverNameHELO.TrimStart("220 ").Split(" ")[0] } else { [String]$serverNameHELO = "" } # ServerOptions $_serverOptions = foreach ($item in $groupData) { if ($item -match "^250\s\s\S+\sHello\s\[\S+]\s(?'ServerOptions'(\S|\s)+)") { $Matches['ServerOptions'] } } if ($_serverOptions) { [string]$serverOptions = [string]::Join(",", $_serverOptions) } else { [string]$serverOptions = "" } # ClientNameHELO $_clientNameHELO = foreach ($item in ($groupData -like "EHLO *" | Select-Object -Unique)) { ([string]$item).trim("EHLO ") } if ($_clientNameHELO) { [string]$clientNameHELO = [string]::Join(",", $_clientNameHELO) } else { [string]$clientNameHELO = "" } # MailFrom [string[]]$_mailFrom = ([array]$groupData -like "MAIL FROM:*") -Split 'MAIL FROM:' | Where-Object { $_ } if ($_mailFrom) { [string[]]$_mailFrom = ($_mailFrom.trim() -Replace '^<', '') | ForEach-Object { $_.split('>')[0] } | Select-Object -Unique | Where-Object { $_ } } if ($_mailFrom) { [string]$mailFrom = [string]::Join(",", $_mailFrom) } else { [string]$mailFrom = "" } # RcptTo [string[]]$_rcptTo = ([array]$groupData -like "RCPT TO:*") -Split 'RCPT TO:' | Where-Object { $_ } if ($_rcptTo) { [string[]]$_rcptTo = ($_rcptTo.trim() -Replace '^<', '') | ForEach-Object { $_.split('>')[0] } | Where-Object { $_ } } if ($_rcptTo) { [string]$rcptTo = [string]::Join(",", $_rcptTo) } else { [string]$rcptTo = "" } # XOOrg [string[]]$_xoorg = foreach ($item in $groupData) { if ($item -match "XOORG=(?'xoorg'\S+)") { $Matches['xoorg'] } } if ($_xoorg) { [string]$xoorg = [string]::Join(",", ($_xoorg.trim() | Select-Object -Unique)) } else { [string]$xoorg = "" } $smtpIdLine = $groupData -match "^250\s2.6.0\s<(?'SmtpId'\S+)" [timespan]$deliveryDuration = [timespan]::new(0) [double]$deliveryBandwidth = 0 [string]$remoteServerHostName = "" [string]$internalId = "" [int]$mailSize = 0 [String]$SmtpId = "" if ($smtpIdLine) { [string[]]$_smtpIdRecords = foreach ($line in $smtpIdLine) { $line.trim("250 2.6.0 <").split(">")[0] } if ($_smtpIdRecords) { $SmtpId = [string]::Join(",", $_smtpIdRecords.trim() ) } else { [string]$smtpId = "" } [string[]]$_remoteServerHostName = foreach ($item in $_smtpIdRecords) { $item.split("@")[1] } if ($_remoteServerHostName) { [string]$remoteServerHostName = [string]::Join(",", ($_remoteServerHostName | Select-Object -Unique) ) } else { [string]$remoteServerHostName = "" } [string[]]$_internalId = $smtpIdLine | ForEach-Object { ($_ -split "InternalId=")[1].split(",")[0] } if ($_internalId) { [string]$internalId = [string]::Join(",", $_internalId.trim() ) } else { [string]$internalId = "" } if ($smtpIdLine -like "*bytes in*") { [int[]]$_mailSize = $smtpIdLine | ForEach-Object { ($_ -split " bytes in ")[0].split(" ")[-1] } if ($_mailSize) { $mailSize = ($_mailSize | Measure-Object -Sum).Sum } else { [int]$mailSize = 0 } ForEach ($item in $smtpIdLine) { $deliveryDuration = $deliveryDuration + [timespan]::FromSeconds( [System.Convert]::ToDouble( (($item -split " bytes in ")[1].split(", ")[0]) , [cultureinfo]::GetCultureInfo('en-us') )) } ForEach ($item in $smtpIdLine) { $deliveryBandwidth = $deliveryBandwidth + [double]::Parse( (($item.TrimEnd(" KB/sec Queued mail for delivery") -split ", ")[-1]) ) $deliveryBandwidth = [math]::Round( ($deliveryBandwidth / $smtpIdLine.count), 3 ) } } } if ($record.Group | Where-Object data -like "Tarpit*") { $tarpitDetect = $true } else { $tarpitDetect = $false } $tarpitDuration = [timespan]::new(0) $tarpitMessage = "" if ($tarpitDetect) { $tarpitDuration = [timespan]::FromSeconds( (($record.Group | where-Object data -like "Tarpit*").data.replace("Tarpit for '", "") | ForEach-Object { $_.split("'")[0] -as [timespan] } | Measure-Object Seconds -Sum).Sum ) $tarpitMessage = (($record.Group | Where-Object data -like "Tarpit*").data -split "(due\sto\s')")[-1].trim("'") } [string]$connectorID = $record.Group[0].'connector-id' if ($connectorID) { if ($connectorID -match "\\") { $connectorName = $connectorID.split("\")[1] } else { $connectorName = $connectorID } } else { $connectorName = "" } if ($connectorID) { $connectorNameWithoutServerName = $connectorName.replace($serverName, "").trim() } else { $connectorNameWithoutServerName = "" } [string]$localEndpoint = $record.Group[-1].'local-endpoint' [string]$remoteEndpoint = $record.Group[-1].'remote-endpoint' if ($groupData -clike "AUTH *") { $authenticationEnabled = $true } else { $authenticationEnabled = $false } $authenticationType = "" $authenticationUser = "" $authenticationMessage = "" if ($authenticationEnabled) { $null = $record.Group | Where-Object data -Match "^AUTH\s(?'Method'\S+)" $authenticationType = $Matches['Method'] $authenticationUser = ($record.Group | Where-Object context -like "authenticated").data $text = @( "235 2.7.0 Authentication successful", "504 5.7.4 Unrecognized authentication type", "535 5.7.3 Authentication unsuccessful" ) [string]$authenticationMessage = ($record.Group | Where-Object data -in $text)[-1].data } # TLS records if ($groupData -clike " CN=*") { $tlsEnabled = $true } else { $tlsEnabled = $false } $tlsAlgorithmEncryption = "" $tlsAlgorithmKeyExchange = "" $tlsAlgorithmMacHash = "" $tlsCertificateRemote = "" $tlsCertificateRemoteIssuer = "" $tlsCertificateRemoteNotAfter = "" $tlsCertificateRemoteNotBefore = "" $tlsCertificateRemoteSAN = "" $tlsCertificateRemoteSerial = "" $tlsCertificateRemoteThumbprint = "" $TlsCertificateServer = "" $tlsCertificateServerIssuer = "" $tlsCertificateServerNotAfter = "" $tlsCertificateServerNotBefore = "" $tlsCertificateServerSAN = "" $tlsCertificateServerSerial = "" $tlsCertificateServerThumbprint = "" $tlsCrypto = "" $tlsDomain = "" $tlsDomainCapabilities = "" $tlsProtocol = "" $tlsStatus = "" $tlsStatusRecord = "" if ($tlsEnabled) { # gather TLS related records $tlsRecords = $record.Group | Where-Object { ($_.event -eq '*') -and ($_.context -like "Sending certificate*" -or $_.context -like "Remote certificate*" -or $_.context -like "TLS protocol*" -or $_.context -like "*TlsDomainCapabilities=*") } | Select-Object 'sequence-number', context, data if ($tlsRecords) { # TLS Crypto string $_tlsCrypto = $TlsRecords | Where-Object context -like "TLS *" | Select-Object -ExpandProperty context -Unique if ($_tlsCrypto) { $tlsCrypto = [string]::Join(",", $_tlsCrypto) } else { $tlsCrypto = "" } if ($tlsCrypto) { # TLS protocol $_tlsProtocol = foreach ($item in $tlsCrypto) { ([string]$item).Replace('TLS protocol ', '').Split(" ")[0] } if ($_tlsProtocol) { $tlsProtocol = [string]::Join(",", $_tlsProtocol) } else { $tlsProtocol = "" } # TLS Algorithm Encryption $_tlsAlgorithmEncryption = foreach ($item in $tlsCrypto) { ([string](([string]$item) -Split "encryption algorithm ")[1]).Split(" ")[0] } if ($_tlsAlgorithmEncryption) { $tlsAlgorithmEncryption = [string]::Join(",", $_tlsAlgorithmEncryption) } else { $tlsAlgorithmEncryption = "" } # TLS Algorithm MacHash $_tlsAlgorithmMacHash = foreach ($item in $tlsCrypto) { ([string](([string]$item) -Split "hash algorithm ")[1]).Split(" ")[0] } if ($_tlsAlgorithmMacHash) { $tlsAlgorithmMacHash = [string]::Join(",", $_tlsAlgorithmMacHash) } else { $tlsAlgorithmMacHash = "" } # TLS Algorithm KeyExchange $_tlsAlgorithmKeyExchange = foreach ($item in $tlsCrypto) { ([string](([string]$item) -Split "exchange algorithm ")[1]).Split(" ")[0] } if ($_tlsAlgorithmKeyExchange) { $tlsAlgorithmKeyExchange = [string]::Join(",", $_tlsAlgorithmKeyExchange) } else { $tlsAlgorithmKeyExchange = "" } } # TLS Server certificate $_tlsCertificateServerRecord = $tlsRecords | where-Object context -Like "Sending certificat*" | Select-Object -First 1 -ExpandProperty data if ($_tlsCertificateServerRecord) { $_tlsCertificateServerRecord = $_tlsCertificateServerRecord.trim() [string]$certText = $_tlsCertificateServerRecord # TLS Server certificate - Subject alternate names [String]$tlsCertificateServerSAN = $certText.split(" ")[-1] $certText = $certText.TrimEnd($tlsCertificateServerSAN).TrimEnd() # TLS Server certificate - Not after [String]$_tlsCertificateServerNotAfter = $certText.split(" ")[-1] $tlsCertificateServerNotAfter = [datetime]::Parse($_tlsCertificateServerNotAfter) $certText = $certText.TrimEnd($_tlsCertificateServerNotAfter).TrimEnd() # TLS Server certificate - Not before [String]$_tlsCertificateServerNotBefore = $certText.split(" ")[-1] $tlsCertificateServerNotBefore = [datetime]::Parse($_tlsCertificateServerNotBefore) $certText = $certText.TrimEnd($_tlsCertificateServerNotBefore).TrimEnd() # TLS Server certificate - Thumbprint [String]$tlsCertificateServerThumbprint = $certText.split(" ")[-1] $certText = $certText.TrimEnd($tlsCertificateServerThumbprint).TrimEnd() # TLS Server certificate - Serial number [String]$tlsCertificateServerSerial = $certText.split(" ")[-1] $certText = $certText.TrimEnd($tlsCertificateServerSerial).TrimEnd() # TLS Server certificate - Issuer name [String]$tlsCertificateServerIssuer = "CN=" + ($certText -split (" CN="))[1] $certText = $certText.TrimEnd($tlsCertificateServerIssuer).TrimEnd() # TLS Server certificate - Subject [String]$tlsCertificateServerIssuer = $certText } [String]$tlsCertificateServer = $_tlsCertificateServerRecord # TLS Remote certificate $_tlsCertificateRemoteRecord = $tlsRecords | where-Object context -Like "Remote certificat*" | Select-Object -First 1 -ExpandProperty data if ($_tlsCertificateRemoteRecord) { $_tlsCertificateRemoteRecord = $_tlsCertificateRemoteRecord.trim() [string]$certText = $_tlsCertificateRemoteRecord # TLS Remote certificate - Subject alternate names [String]$tlsCertificateRemoteSAN = $certText.split(" ")[-1] $certText = $certText.TrimEnd($tlsCertificateRemoteSAN).TrimEnd() # TLS Remote certificate - Not after [String]$_tlsCertificateRemoteNotAfter = $certText.split(" ")[-1] $tlsCertificateRemoteNotAfter = [datetime]::Parse($_tlsCertificateRemoteNotAfter) $certText = $certText.TrimEnd($_tlsCertificateRemoteNotAfter).TrimEnd() # TLS Remote certificate - Not before [String]$_tlsCertificateRemoteNotBefore = $certText.split(" ")[-1] $tlsCertificateRemoteNotBefore = [datetime]::Parse($_tlsCertificateRemoteNotBefore) $certText = $certText.TrimEnd($_tlsCertificateRemoteNotBefore).TrimEnd() # TLS Remote certificate - Thumbprint [String]$tlsCertificateRemoteThumbprint = $certText.split(" ")[-1] $certText = $certText.TrimEnd($tlsCertificateRemoteThumbprint).TrimEnd() # TLS Remote certificate - Serial number [String]$tlsCertificateRemoteSerial = $certText.split(" ")[-1] $certText = $certText.TrimEnd($tlsCertificateRemoteSerial).TrimEnd() # TLS Remote certificate - Issuer name [String]$tlsCertificateRemoteIssuer = "CN=" + ($certText -split (" CN="))[1] $certText = $certText.TrimEnd($tlsCertificateRemoteIssuer).TrimEnd() # TLS Remote certificate - Subject [String]$tlsCertificateRemoteIssuer = $certText } [String]$tlsCertificateRemote = $_tlsCertificateRemoteRecord # TLS Status Record $_tlsStatusRecord = ($tlsRecords | Where-Object context -Like "*; Status='*").context -Split "; " if ($_tlsStatusRecord) { # TLS Status Record [String]$tlsStatusRecord = [string]::Join("; ", $_tlsStatusRecord) # TLS Domain Capabilities $_tlsDomainCapabilities = ("" + (($_tlsStatusRecord | Where-Object { $_ -like "TlsDomainCapabilities=*" }) -split "=")[1] ).trim("'") if ($_tlsDomainCapabilities) { $tlsDomainCapabilities = [string]::Join(",", $_tlsDomainCapabilities) } else { $tlsDomainCapabilities = "" } # TLS Status $_tlsStatus = ("" + (($_tlsStatusRecord | Where-Object { $_ -like "Status=*" }) -split "=")[1] ).trim("'") if ($_tlsStatus) { $tlsStatus = [string]::Join(",", $_tlsStatus) } else { $tlsStatus = "" } # TLS Domain $_tlsDomain = ("" + (($_tlsStatusRecord | Where-Object { $_ -like "Domain=*" }) -split "=")[1] ).trim("'") if ($_tlsDomain) { $tlsDomain = [string]::Join(",", $_tlsDomain) } else { $tlsDomain = "" } } else { [String]$_tlsStatusRecord = "" } } } # final status $_finalStatus = $record.Group[-1].context if ($_finalStatus -like "Local") { $finalStatus = "OK" } elseif ($_finalStatus) { $finalStatus = $_finalStatus } else { $finalStatus = "UNKNOWN" } # construct output object $outputRecord = [PSCustomObject]@{ "PSTypeName" = "ExchangeLog.$($record.metadataHash['Log-type'].Replace(' ','')).Record" "LogFolder" = $record.LogFolder "LogFileName" = $record.LogFileName $SessionIdName = $record.$SessionIdName "DateStart" = ($record.Group | Sort-Object 'date-time')[0].'date-time' -as [datetime] "DateEnd" = ($record.Group | Sort-Object 'date-time')[-1].'date-time' -as [datetime] "SequenceCount" = $record.Group.count "ConnectorID" = $ConnectorID "ServerName" = $ServerName "ConnectorName" = $ConnectorName "ConnectorNameWithoutServerName" = $ConnectorNameWithoutServerName "LocalIP" = $localEndpoint -replace ":$([string]$localEndpoint.split(":")[-1])", "" "LocalPort" = $localEndpoint.split(":")[-1] "RemoteIP" = $remoteEndpoint -replace ":$([string]$remoteEndpoint.split(":")[-1])", "" "RemotePort" = $remoteEndpoint.split(":")[-1] "ServerNameHELO" = $ServerNameHELO "ServerOptions" = $ServerOptions "ClientNameHELO" = $clientNameHELO "TlsEnabled" = $TlsEnabled "AuthenticationEnabled" = $AuthenticationEnabled "AuthenticationType" = $AuthenticationType "AuthenticationUser" = $AuthenticationUser "AuthenticationMessage" = $AuthenticationMessage "TarpitDetect" = $TarpitDetect "TarpitDuration" = $TarpitDuration "TarpitMessage" = $TarpitMessage "MailFrom" = $MailFrom "RcptTo" = $rcptTo "XOOrg" = $XOOrg "SmtpId" = $SmtpId "RemoteServerHostName" = $RemoteServerHostName "InternalId" = $InternalId "MailSize" = $MailSize "DeliveryDuration" = $DeliveryDuration "DeliveryBandwidth" = $deliveryBandwidth "FinalSessionStatus" = $finalStatus "FinalizeMessage" = $groupData[-2] "TlsProtocol" = $tlsProtocol "TlsAlgorithmEncryption" = $tlsAlgorithmEncryption "TlsAlgorithmMacHash" = $tlsAlgorithmMacHash "TlsAlgorithmKeyExchange" = $tlsAlgorithmKeyExchange "TlsCertificateServer" = $tlsCertificateServer "TlsCertificateServerIssuer" = $tlsCertificateServerIssuer "TlsCertificateServerNotAfter" = $tlsCertificateServerNotAfter "TlsCertificateServerNotBefore" = $tlsCertificateServerNotBefore "TlsCertificateServerSAN" = $tlsCertificateServerSAN "TlsCertificateServerSerial" = $tlsCertificateServerSerial "TlsCertificateServerThumbprint" = $tlsCertificateServerThumbprint "TlsCertificateRemote" = $tlsCertificateRemote "TlsCertificateRemoteIssuer" = $tlsCertificateRemoteIssuer "TlsCertificateRemoteNotAfter" = $tlsCertificateRemoteNotAfter "TlsCertificateRemoteNotBefore" = $tlsCertificateRemoteNotBefore "TlsCertificateRemoteSAN" = $tlsCertificateRemoteSAN "TlsCertificateRemoteSerial" = $tlsCertificateRemoteSerial "TlsCertificateRemoteThumbprint" = $tlsCertificateRemoteThumbprint "TlsStatus" = $tlsStatus "TlsDomain" = $tlsDomain "TlsDomainCapabilities" = $tlsDomainCapabilities "TlsCrypto" = $tlsCrypto "TlsStatusRecord" = $tlsStatusRecord "LogText" = $logText } # add metadata attributes foreach ($key in $record.metadataHash.Keys) { if ($key -like "Date") { $value = $record.metadataHash[$key] -as [datetime] } else { $value = $record.metadataHash[$key] } $outputRecord | Add-Member -MemberType NoteProperty -Name $key -Value $value -Force } # output data $outputRecord # report in detail if errors occur (for debugging because the processing in operating in runspaces) if ($Error) { Write-Warning "Error detected while processing $($outputRecord.LogFolder)\$($outputRecord.LogFileName) with $($record.$SessionIdName)" $Error.Clear() } # output progress of switch is set (only debugging purpose) if ($ShowProgress) { if (($i % $refreshInterval) -eq 0) { Write-Progress -Activity "Process logfile record " -Status "$($record.LogFileName) - $($SessionIdName): $($record.$SessionIdName) ($($i) / $($InputObject.count))" -PercentComplete ($i / $InputObject.count * 100) } $i = $i + 1 } } } end { } } (Get-Command Expand-LogRecordSmtp).Visibility = "Private" |