This one has come up a couple of times for me over the last couple of weeks so I thought I'd put together a post to expand on the Subject a little.
The easiest place to start before talking about Exchange is to look at a ordinary MIME message and its associated headers. In a MIME message there is one Message date header http://tools.ietf.org/html/rfc4021#section-2.1.1 which outlines "Specifies the date and time at which the creator of the message indicated that the message was complete and ready to enter the mail delivery system" which is a way of saying that its the Sent Time. eg In a Message
Subject: Re: Oh no
Date: Thu, 14 Aug 2014 18:44:52 +1000
Message-ID: <nk0h69wn6waj8s32rnc3kma0.1408005882691@email.android.com>
As the Message traverses various MTA's along the way to its final destinations, Received headers with dates are added to the Transport Headers of a message indicating the date\time a particular MTA's processed the message eg
Received: from BY2PR03MB459.namprd03.prod.outlook.com (10.141.141.147) by
DM2PR03MB463.namprd03.prod.outlook.com (10.141.85.20) with Microsoft SMTP
Server (TLS) id 15.0.859.15 via Mailbox Transport; Wed, 29 Jan 2014 22:06:05
+0000
When the Message finally arrives at is destination and is delivered by the Store to a users Mailbox two MAPI properties will be created to reflect the Sent Time and also the Date Time the message was delivered by the store (which should match (most recent) received header in the message). With POP3 and and some POP downloaders this is where the date can get a little offset and not represent the real time of message delivery. But sticking to Exchange the following two props get set
PR_MESSAGE_DELIVERY_TIME which in EWS is also represented by the Strongly typed DateTimeReceived property
PR_CLIENT_SUBMIT_TIME property which in EWS is represented by the Strongly typed DateTimeSent property.
Exchange when it stores these dates like with other dates will store these in UTC format. When your using Outlook with the default views it will use the PR_MESSAGE_DELIVERY_TIME for every Mail folder other then the Sent Items where the view will use the PR_CLIENT_SUBMIT_TIME.
So whenever you importing EML's and you see an unexpected Received (or Sent) by date the first thing to check is the Transport Headers and look at the most recent received header. If your missing these headers then that maybe why your dates aren't what you expect.
In EWS you can access the Transport Headers via the InternetMessageHeaders strongly typed property or you can use the PR_TRANSPORT_MESSAGE_HEADERS extended properties. Depending on the version of Exchange you are using there can be issues with the strongly typed property so you should read http://msdn.microsoft.com/EN-US/library/office/hh545614(v=exchg.140).aspx . Because of the size of these properties this information will only be returned when you use a GetItem's operation
The other thing you can do with these message dates is track the amount of time it took from submit to the delivery of a message eg the following script will use EWS to grab both the PR_MESSAGE_DELIVERY_TIME and PR_CLIENT_SUBMIT_TIME MAPI properties and use those to calculate the delivery time is also parses the Sent Header datetime and creates a CSV of the output. I've put a download of this script here the code looks like
The easiest place to start before talking about Exchange is to look at a ordinary MIME message and its associated headers. In a MIME message there is one Message date header http://tools.ietf.org/html/rfc4021#section-2.1.1 which outlines "Specifies the date and time at which the creator of the message indicated that the message was complete and ready to enter the mail delivery system" which is a way of saying that its the Sent Time. eg In a Message
Subject: Re: Oh no
Date: Thu, 14 Aug 2014 18:44:52 +1000
Message-ID: <nk0h69wn6waj8s32rnc3kma0.1408005882691@email.android.com>
As the Message traverses various MTA's along the way to its final destinations, Received headers with dates are added to the Transport Headers of a message indicating the date\time a particular MTA's processed the message eg
Received: from BY2PR03MB459.namprd03.prod.outlook.com (10.141.141.147) by
DM2PR03MB463.namprd03.prod.outlook.com (10.141.85.20) with Microsoft SMTP
Server (TLS) id 15.0.859.15 via Mailbox Transport; Wed, 29 Jan 2014 22:06:05
+0000
When the Message finally arrives at is destination and is delivered by the Store to a users Mailbox two MAPI properties will be created to reflect the Sent Time and also the Date Time the message was delivered by the store (which should match (most recent) received header in the message). With POP3 and and some POP downloaders this is where the date can get a little offset and not represent the real time of message delivery. But sticking to Exchange the following two props get set
PR_MESSAGE_DELIVERY_TIME which in EWS is also represented by the Strongly typed DateTimeReceived property
PR_CLIENT_SUBMIT_TIME property which in EWS is represented by the Strongly typed DateTimeSent property.
Exchange when it stores these dates like with other dates will store these in UTC format. When your using Outlook with the default views it will use the PR_MESSAGE_DELIVERY_TIME for every Mail folder other then the Sent Items where the view will use the PR_CLIENT_SUBMIT_TIME.
So whenever you importing EML's and you see an unexpected Received (or Sent) by date the first thing to check is the Transport Headers and look at the most recent received header. If your missing these headers then that maybe why your dates aren't what you expect.
In EWS you can access the Transport Headers via the InternetMessageHeaders strongly typed property or you can use the PR_TRANSPORT_MESSAGE_HEADERS extended properties. Depending on the version of Exchange you are using there can be issues with the strongly typed property so you should read http://msdn.microsoft.com/EN-US/library/office/hh545614(v=exchg.140).aspx . Because of the size of these properties this information will only be returned when you use a GetItem's operation
The other thing you can do with these message dates is track the amount of time it took from submit to the delivery of a message eg the following script will use EWS to grab both the PR_MESSAGE_DELIVERY_TIME and PR_CLIENT_SUBMIT_TIME MAPI properties and use those to calculate the delivery time is also parses the Sent Header datetime and creates a CSV of the output. I've put a download of this script here the code looks like
- ## Get the Mailbox to Access from the 1st commandline argument
- $MailboxName = $args[0]
- ## Load Managed API dll
- ###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT
- $EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")
- if (Test-Path $EWSDLL)
- {
- Import-Module $EWSDLL
- }
- else
- {
- "$(get-date -format yyyyMMddHHmmss):"
- "This script requires the EWS Managed API 1.2 or later."
- "Please download and install the current version of the EWS Managed API from"
- "http://go.microsoft.com/fwlink/?LinkId=255472"
- ""
- "Exiting Script."
- exit
- }
- ## Set Exchange Version
- $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2
- ## Create Exchange Service Object
- $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
- ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials
- #Credentials Option 1 using UPN for the windows Account
- $psCred = Get-Credential
- $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())
- $service.Credentials = $creds
- #Credentials Option 2
- #service.UseDefaultCredentials = $true
- ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates
- ## Code From http://poshcode.org/624
- ## Create a compilation environment
- $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
- $Compiler=$Provider.CreateCompiler()
- $Params=New-Object System.CodeDom.Compiler.CompilerParameters
- $Params.GenerateExecutable=$False
- $Params.GenerateInMemory=$True
- $Params.IncludeDebugInformation=$False
- $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null
- $TASource=@'
- namespace Local.ToolkitExtensions.Net.CertificatePolicy{
- public class TrustAll : System.Net.ICertificatePolicy {
- public TrustAll() {
- }
- public bool CheckValidationResult(System.Net.ServicePoint sp,
- System.Security.Cryptography.X509Certificates.X509Certificate cert,
- System.Net.WebRequest req, int problem) {
- return true;
- }
- }
- }
- '@
- $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
- $TAAssembly=$TAResults.CompiledAssembly
- ## We now create an instance of the TrustAll and attach it to the ServicePointManager
- $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
- [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll
- ## end code from http://poshcode.org/624
- ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use
- #CAS URL Option 1 Autodiscover
- $service.AutodiscoverUrl($MailboxName,{$true})
- "Using CAS Server : " + $Service.url
- #CAS URL Option 2 Hardcoded
- #$uri=[system.URI] "https://casservername/ews/exchange.asmx"
- #$service.Url = $uri
- ## Optional section for Exchange Impersonation
- #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
- # Bind to the Inbox Folder
- $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
- $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- $PR_MESSAGE_DELIVERY_TIME = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0E06, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::SystemTime)
- $PR_CLIENT_SUBMIT_TIME = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0039, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::SystemTime)
- $PR_TRANSPORT_MESSAGE_HEADERS = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x007D,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
- $psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly)
- $psPropset.Add($PR_CLIENT_SUBMIT_TIME)
- $psPropset.Add($PR_MESSAGE_DELIVERY_TIME)
- $psPropset.Add($PR_TRANSPORT_MESSAGE_HEADERS)
- $psPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject)
- #Define ItemView to retrive just 1000 Items
- $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
- $ivItemView.PropertySet = $psPropset
- $rptCollection = @()
- $fiItems = $null
- do{
- $fiItems = $service.FindItems($Inbox.Id,$ivItemView)
- [Void]$service.LoadPropertiesForItems($fiItems,$psPropset)
- foreach($Item in $fiItems.Items){
- $Headers = $null;
- $ClientSubmitTime = $null
- $DeliveryTime = $null
- [Void]$Item.TryGetProperty($PR_CLIENT_SUBMIT_TIME,[ref]$ClientSubmitTime)
- [Void]$Item.TryGetProperty($PR_MESSAGE_DELIVERY_TIME,[ref]$DeliveryTime)
- if($Item.TryGetProperty($PR_TRANSPORT_MESSAGE_HEADERS,[ref]$Headers)){
- $slen = $Headers.ToLower().IndexOf("`ndate: ")
- if($slen -gt 0){
- $elen = $Headers.IndexOf("`r`n",$slen)
- $TimeSpan = NEW-TIMESPAN –Start $ClientSubmitTime –End $DeliveryTime
- $rptobj = "" | select Subject,HeaderDate,DELIVERY_TIME,SUBMIT_TIME,Diff
- $rptobj.Subject = $Item.Subject
- $parsedDate = $Headers.Substring(($slen+7),($elen-($slen+7)))
- $rptobj.HeaderDate = [DateTime]::Parse($parsedDate).ToLocalTime()
- $rptobj.DELIVERY_TIME = $DeliveryTime.ToLocalTime()
- $rptobj.SUBMIT_TIME = $ClientSubmitTime.ToLocalTime()
- $rptobj.Diff = [Math]::Round($TimeSpan.TotalMinutes,0)
- $rptCollection += $rptobj
- }
- }
- }
- $ivItemView.Offset += $fiItems.Items.Count
- Write-Host ("Processed " + $ivItemView.Offset + " of " + $fiItems.TotalCount)
- }while($fiItems.MoreAvailable -eq $true)
- $rptCollection | Export-Csv -NoTypeInformation -Path "c:\Temp\$mailboxName-mTimes.csv"
- Write-Host("Exported to c:\Temp\$mailboxName-mTimes.csv")