diff --git a/.gitignore b/.gitignore index 800cdf0..bbdbaa3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,179 +1,179 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.sln.docstates - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -x64/ -build/ -bld/ -[Bb]in/ -[Oo]bj/ - -# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets -!packages/*/build/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -#NUNIT -*.VisualState.xml -TestResult.xml - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding addin-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -*.ncrunch* -_NCrunch_* -.*crunch*.local.xml - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml - -# NuGet Packages Directory -## TODO: If you have NuGet Package Restore enabled, uncomment the next line -#packages/ -## TODO: If the tool you use requires repositories.config, also uncomment the next line -#!packages/repositories.config - -# Windows Azure Build Output -csx/ -*.build.csdef - -# Windows Store app package directory -AppPackages/ - -# Others -sql/ -*.Cache -ClientBin/ -[Ss]tyle[Cc]op.* -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.pfx -*.publishsettings - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file to a newer -# Visual Studio version. Backup files are not needed, because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -App_Data/*.mdf -App_Data/*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# ========================= -# Windows detritus -# ========================= - -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +x64/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ +## TODO: If the tool you use requires repositories.config, also uncomment the next line +#!packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares $RECYCLE.BIN/ \ No newline at end of file diff --git a/EwsMailDl.sln b/EwsMailDl.sln index ce257df..85abff9 100644 --- a/EwsMailDl.sln +++ b/EwsMailDl.sln @@ -1,26 +1,26 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual C# Express 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EwsMailDl", "EwsMailDl\EwsMailDl.csproj", "{EA83B646-A276-44EB-A735-67D61738531B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {EA83B646-A276-44EB-A735-67D61738531B}.Debug|x64.ActiveCfg = Debug|x64 - {EA83B646-A276-44EB-A735-67D61738531B}.Debug|x64.Build.0 = Debug|x64 - {EA83B646-A276-44EB-A735-67D61738531B}.Debug|x86.ActiveCfg = Debug|x86 - {EA83B646-A276-44EB-A735-67D61738531B}.Debug|x86.Build.0 = Debug|x86 - {EA83B646-A276-44EB-A735-67D61738531B}.Release|x64.ActiveCfg = Release|x64 - {EA83B646-A276-44EB-A735-67D61738531B}.Release|x64.Build.0 = Release|x64 - {EA83B646-A276-44EB-A735-67D61738531B}.Release|x86.ActiveCfg = Release|x86 - {EA83B646-A276-44EB-A735-67D61738531B}.Release|x86.Build.0 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual C# Express 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EwsMailDl", "EwsMailDl\EwsMailDl.csproj", "{EA83B646-A276-44EB-A735-67D61738531B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EA83B646-A276-44EB-A735-67D61738531B}.Debug|x64.ActiveCfg = Debug|x64 + {EA83B646-A276-44EB-A735-67D61738531B}.Debug|x64.Build.0 = Debug|x64 + {EA83B646-A276-44EB-A735-67D61738531B}.Debug|x86.ActiveCfg = Debug|x86 + {EA83B646-A276-44EB-A735-67D61738531B}.Debug|x86.Build.0 = Debug|x86 + {EA83B646-A276-44EB-A735-67D61738531B}.Release|x64.ActiveCfg = Release|x64 + {EA83B646-A276-44EB-A735-67D61738531B}.Release|x64.Build.0 = Release|x64 + {EA83B646-A276-44EB-A735-67D61738531B}.Release|x86.ActiveCfg = Release|x86 + {EA83B646-A276-44EB-A735-67D61738531B}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/EwsMailDl/EmailDownloader.cs b/EwsMailDl/EmailDownloader.cs index 18324fb..194e95d 100644 --- a/EwsMailDl/EmailDownloader.cs +++ b/EwsMailDl/EmailDownloader.cs @@ -1,190 +1,190 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading; -using Microsoft.Exchange.WebServices.Data; - -namespace EwsMailDl -{ - class EmailDownloader - { - private EventLog eventLog; - - private List emailIdCache = new List(10); - - private CancellationTokenSource tokenSource; - - private BlockingCollection emailIdQueue; - - private ExchangeService exchangeService; - - private string savePath; - - private IList subjectFilters; - - private bool timestamp; - - public EmailDownloader(EventLog eventLog, BlockingCollection emailIdQueue, CancellationTokenSource tokenSource, Settings settings) - { - this.eventLog = eventLog; - this.tokenSource = tokenSource; - this.emailIdQueue = emailIdQueue; - this.exchangeService = settings.CreateExchangeService(); - this.savePath = settings.SavePath; - this.subjectFilters = settings.SubjectFilters; - this.timestamp = settings.Timestamp; - } - - public void Run() - { - while (!tokenSource.IsCancellationRequested && !emailIdQueue.IsAddingCompleted) - { - ItemId emailId = null; - - try - { - emailId = emailIdQueue.Take(tokenSource.Token); - } - catch (OperationCanceledException) - { - if (tokenSource.IsCancellationRequested) - { - if (Environment.UserInteractive) - { - Console.WriteLine("Downloader cancelled!"); - } - - break; - } - } - - if (emailId == null || emailIdCache.Contains(emailId.UniqueId)) - { - if (Environment.UserInteractive) - { - Console.WriteLine("Ignoring a duplicate e-mail: {0}", emailId); - } - - continue; - } - - if (emailIdCache.Count == emailIdCache.Capacity) - { - emailIdCache.RemoveAt(0); - } - - emailIdCache.Add(emailId.UniqueId); - - EmailMessage email = null; - - try - { - email = EmailMessage.Bind( - exchangeService, - emailId, - new PropertySet( - EmailMessageSchema.Subject, - EmailMessageSchema.Attachments, - EmailMessageSchema.DateTimeReceived - ) - ); - } - catch (Exception) { } - - if (email == null) - { - continue; - } - - if (MatchEmail(email)) - { - if (Environment.UserInteractive) - { - Console.WriteLine("Processing a new matching e-mail: {0}", email.Subject); - } - - DownloadAndDelete(email); - } - else if (Environment.UserInteractive && email != null) - { - Console.WriteLine("Ignoring a not matching e-mail: {0}", email.Subject); - } - } - } - - private bool MatchEmail(EmailMessage email) - { - return email.Attachments.Count > 0 - && (subjectFilters.Count == 0 || subjectFilters.Any(phrase => email.Subject.ToLower().Contains(phrase.ToLower()))); - } - - private void DownloadAndDelete(EmailMessage email) - { - foreach (var attachment in email.Attachments) - { - if (!(attachment is FileAttachment)) - { - continue; - } - - var fileAttachment = attachment as FileAttachment; - - try - { - if (Environment.UserInteractive) - { - Console.WriteLine("Downloading an attachment: {0}", fileAttachment.Name); - } - - var filePath = CreateFilePath(email.DateTimeReceived, fileAttachment.Name); - - fileAttachment.Load(filePath); - File.SetCreationTime(filePath, email.DateTimeReceived); - } - catch (Exception x) - { - HandleException("Failed to download the attachment", x); - } - } - - try - { - if (Environment.UserInteractive) - { - Console.WriteLine("Deleting the e-mail..."); - } - - email.Delete(DeleteMode.HardDelete); - } - catch (Exception x) - { - HandleException("Failed to delete the e-mail", x); - } - } - - private string CreateFilePath(DateTime dateTime, string fileName) - { - if (timestamp) - { - fileName = String.Format("{0}@{1}", (dateTime - new DateTime(1970, 1, 1).ToLocalTime()).TotalSeconds, fileName); - } - - return Path.Combine(savePath, fileName); - } - - private void HandleException(string prefix, Exception x) - { - if (Environment.UserInteractive) - { - Console.WriteLine(prefix + ": " + x); - } - else - { - this.eventLog.WriteEntry(prefix + ": " + x, EventLogEntryType.Warning); - } - } - } -} +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using Microsoft.Exchange.WebServices.Data; + +namespace EwsMailDl +{ + class EmailDownloader + { + private EventLog eventLog; + + private List emailIdCache = new List(10); + + private CancellationTokenSource tokenSource; + + private BlockingCollection emailIdQueue; + + private ExchangeService exchangeService; + + private string savePath; + + private IList subjectFilters; + + private bool timestamp; + + public EmailDownloader(EventLog eventLog, BlockingCollection emailIdQueue, CancellationTokenSource tokenSource, Settings settings) + { + this.eventLog = eventLog; + this.tokenSource = tokenSource; + this.emailIdQueue = emailIdQueue; + this.exchangeService = settings.CreateExchangeService(); + this.savePath = settings.SavePath; + this.subjectFilters = settings.SubjectFilters; + this.timestamp = settings.Timestamp; + } + + public void Run() + { + while (!tokenSource.IsCancellationRequested && !emailIdQueue.IsAddingCompleted) + { + ItemId emailId = null; + + try + { + emailId = emailIdQueue.Take(tokenSource.Token); + } + catch (OperationCanceledException) + { + if (tokenSource.IsCancellationRequested) + { + if (Environment.UserInteractive) + { + Console.WriteLine("Downloader cancelled!"); + } + + break; + } + } + + if (emailId == null || emailIdCache.Contains(emailId.UniqueId)) + { + if (Environment.UserInteractive) + { + Console.WriteLine("Ignoring a duplicate e-mail: {0}", emailId); + } + + continue; + } + + if (emailIdCache.Count == emailIdCache.Capacity) + { + emailIdCache.RemoveAt(0); + } + + emailIdCache.Add(emailId.UniqueId); + + EmailMessage email = null; + + try + { + email = EmailMessage.Bind( + exchangeService, + emailId, + new PropertySet( + EmailMessageSchema.Subject, + EmailMessageSchema.Attachments, + EmailMessageSchema.DateTimeReceived + ) + ); + } + catch (Exception) { } + + if (email == null) + { + continue; + } + + if (MatchEmail(email)) + { + if (Environment.UserInteractive) + { + Console.WriteLine("Processing a new matching e-mail: {0}", email.Subject); + } + + DownloadAndDelete(email); + } + else if (Environment.UserInteractive && email != null) + { + Console.WriteLine("Ignoring a not matching e-mail: {0}", email.Subject); + } + } + } + + private bool MatchEmail(EmailMessage email) + { + return email.Attachments.Count > 0 + && (subjectFilters.Count == 0 || subjectFilters.Any(phrase => email.Subject.ToLower().Contains(phrase.ToLower()))); + } + + private void DownloadAndDelete(EmailMessage email) + { + foreach (var attachment in email.Attachments) + { + if (!(attachment is FileAttachment)) + { + continue; + } + + var fileAttachment = attachment as FileAttachment; + + try + { + if (Environment.UserInteractive) + { + Console.WriteLine("Downloading an attachment: {0}", fileAttachment.Name); + } + + var filePath = CreateFilePath(email.DateTimeReceived, fileAttachment.Name); + + fileAttachment.Load(filePath); + File.SetCreationTime(filePath, email.DateTimeReceived); + } + catch (Exception x) + { + HandleException("Failed to download the attachment", x); + } + } + + try + { + if (Environment.UserInteractive) + { + Console.WriteLine("Deleting the e-mail..."); + } + + email.Delete(DeleteMode.HardDelete); + } + catch (Exception x) + { + HandleException("Failed to delete the e-mail", x); + } + } + + private string CreateFilePath(DateTime dateTime, string fileName) + { + if (timestamp) + { + fileName = String.Format("{0}@{1}", (dateTime - new DateTime(1970, 1, 1).ToLocalTime()).TotalSeconds, fileName); + } + + return Path.Combine(savePath, fileName); + } + + private void HandleException(string prefix, Exception x) + { + if (Environment.UserInteractive) + { + Console.WriteLine(prefix + ": " + x); + } + else + { + this.eventLog.WriteEntry(prefix + ": " + x, EventLogEntryType.Warning); + } + } + } +} diff --git a/EwsMailDl/EmailSender.cs b/EwsMailDl/EmailSender.cs new file mode 100644 index 0000000..e0cb53f --- /dev/null +++ b/EwsMailDl/EmailSender.cs @@ -0,0 +1,372 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using Microsoft.Exchange.WebServices.Data; +using System.Text; +using System.Text.RegularExpressions; + +namespace EwsMailDl +{ + class EmailSender + { + private List cache = new List(10); + + private BlockingCollection queue = new BlockingCollection(); + + private EventLog eventLog; + + private CancellationTokenSource tokenSource; + + private ExchangeService exchangeService; + + private FileSystemWatcher watcher; + + public EmailSender(EventLog eventLog, CancellationTokenSource tokenSource, Settings settings) + { + this.eventLog = eventLog; + this.tokenSource = tokenSource; + this.exchangeService = settings.CreateExchangeService(); + + watcher = new FileSystemWatcher(settings.InputPath); + watcher.Filter = "*.email"; + watcher.Created += OnEmailFileCreated; + } + + public void Run() + { + foreach (var file in Directory.GetFiles(watcher.Path, watcher.Filter, SearchOption.TopDirectoryOnly)) + { + queue.Add(Path.Combine(watcher.Path, file)); + } + + watcher.EnableRaisingEvents = true; + + while (!tokenSource.IsCancellationRequested && !queue.IsAddingCompleted) + { + string emailFullPath = null; + + try + { + emailFullPath = queue.Take(tokenSource.Token); + } + catch (OperationCanceledException) + { + if (tokenSource.IsCancellationRequested) + { + if (Environment.UserInteractive) + { + Console.WriteLine("Sender cancelled!"); + } + + break; + } + } + + if (emailFullPath == null) + { + continue; + } + + if (cache.Contains(emailFullPath)) + { + if (Environment.UserInteractive) + { + Console.WriteLine("Ignoring a duplicate e-mail: {0}", emailFullPath); + } + + continue; + } + + if (cache.Count == cache.Capacity) + { + cache.RemoveAt(0); + } + + cache.Add(emailFullPath); + + EmailMessage email = null; + + try + { + email = CreateEmailMessage(emailFullPath); + } + catch (Exception x) + { + HandleException("Failed to create an e-mail message from " + emailFullPath, x); + } + + if (email == null) + { + continue; + } + + Send(email); + + try + { + File.Delete(emailFullPath); + } + catch (Exception x) + { + HandleException("Failed to delete an e-mail file " + emailFullPath, x); + } + } + + watcher.EnableRaisingEvents = false; + queue.CompleteAdding(); + } + + private void OnEmailFileCreated(object sender, FileSystemEventArgs e) + { + if (!queue.IsAddingCompleted && e.ChangeType == WatcherChangeTypes.Created) + { + if (Environment.UserInteractive) + { + Console.WriteLine("Queued e-mail to send: {0}", e.FullPath); + } + + queue.Add(e.FullPath); + } + } + + private EmailMessage CreateEmailMessage(string emailFullPath, int retry = 0) + { + string rawEmail = null; + + try + { + var fileStream = new FileStream(emailFullPath, FileMode.Open, FileAccess.Read, FileShare.None); + var rawEmailBuffer = new byte[fileStream.Length]; + + fileStream.Read(rawEmailBuffer, 0, rawEmailBuffer.Length); + fileStream.Close(); + + rawEmail = Encoding.UTF8.GetString(rawEmailBuffer); + } + catch (Exception x) + { + if (retry == 3) + { + HandleException("Failed to read the e-mail file", x); + queue.Add(emailFullPath); + } + else + { + Thread.Sleep(1000); + + return CreateEmailMessage(emailFullPath, retry + 1); + } + } + + if (string.IsNullOrWhiteSpace(rawEmail)) + { + return null; + } + + var bodyIndex = rawEmail.IndexOf("Body:"); + + if (bodyIndex == -1) + { + return null; + } + + var headers = ParseHeaders(rawEmail.Substring(0, bodyIndex).Trim()); + var newLineIndex = rawEmail.IndexOf('\n', bodyIndex); + var body = rawEmail.Substring(newLineIndex + 1).Trim(); + + if (headers.Count == 0 || string.IsNullOrWhiteSpace(body)) + { + return null; + } + + var htmlBody = headers.ContainsKey("html") && (headers["html"].Equals("1") || headers["html"].Equals("true")); + + if (!htmlBody) + { + body = string.Join( + "
", + body.Split('\n').Select(line => + { + line = Regex.Replace(line, "^[\r\t]+", ""); + + var matches = Regex.Match(line, "^( +)"); + + line = line.Trim(); + + if (matches.Success) + { + var spaceCount = matches.Groups[1].Value.Length; + + for (var i = 0; i < spaceCount; ++i) + { + line = " " + line; + } + } + + return line; + }) + ); + } + + var email = new EmailMessage(exchangeService); + email.Subject = ""; + email.Body = new MessageBody(BodyType.HTML, body); + + foreach (var header in headers) + { + ApplyHeader(email, header.Key, header.Value); + } + + if (string.IsNullOrWhiteSpace(email.Subject) || email.ToRecipients.Count == 0) + { + return null; + } + + return email; + } + + private IDictionary ParseHeaders(string rawHeaders) + { + var headers = new Dictionary(); + + if (string.IsNullOrWhiteSpace(rawHeaders)) + { + return headers; + } + + foreach (var headerLine in rawHeaders.Split('\n')) + { + var parts = headerLine.Split(new char[] { ':' }, 2); + + if (parts.Length != 2) + { + continue; + } + + var headerName = parts[0].Trim().ToLower(); + var headerValue = parts[1].Trim(); + + headers[headerName] = headerValue; + } + + return headers; + } + + private void ApplyHeader(EmailMessage email, string header, string rawValue) + { + switch (header) + { + case "subject": + email.Subject = rawValue; + break; + + case "importance": + email.Importance = rawValue.Equals("low", StringComparison.InvariantCultureIgnoreCase) + ? Importance.Low + : rawValue.Equals("high", StringComparison.InvariantCultureIgnoreCase) + ? Importance.High + : Importance.Normal; + break; + + case "to": + case "torecipients": + AddEmailAddresses(email.ToRecipients, rawValue); + break; + + case "cc": + case "ccrecipients": + AddEmailAddresses(email.CcRecipients, rawValue); + break; + + case "bcc": + case "bccrecipients": + AddEmailAddresses(email.BccRecipients, rawValue); + break; + + case "from": + email.From = CreateEmailAddress(rawValue); + break; + + case "replyto": + case "reply-to": + AddEmailAddresses(email.ReplyTo, rawValue); + break; + } + } + + private EmailAddress CreateEmailAddress(string rawValue) + { + var match = Regex.Match(rawValue, @"^(.*?)\s*(?:<(.*?)>)?$"); + + if (!match.Success) + { + return null; + } + + var emailAddress = new EmailAddress(); + + if (string.IsNullOrWhiteSpace(match.Groups[2].Value)) + { + emailAddress.Address = match.Groups[1].Value.Trim(); + } + else if (match.Groups[2].Value.IndexOf('@') == -1) + { + emailAddress.Address = match.Groups[1].Value.Trim(); + emailAddress.Name = match.Groups[2].Value.Trim(); + } + else + { + emailAddress.Address = match.Groups[2].Value.Trim(); + emailAddress.Name = match.Groups[1].Value.Trim(); + } + + return emailAddress; + } + + private void AddEmailAddresses(EmailAddressCollection emailAddresses, string rawValue) + { + foreach (var rawEmailAddress in rawValue.Split(new char[] { ',' })) + { + var emailAddress = CreateEmailAddress(rawEmailAddress); + + if (emailAddresses != null) + { + emailAddresses.Add(emailAddress); + } + } + } + + private void Send(EmailMessage email) + { + try + { + if (Environment.UserInteractive) + { + Console.WriteLine("Sending the e-mail '{0}' to '{1}'...", email.Subject, String.Join(", ", email.ToRecipients.ToArray())); + } + + email.Send(); + } + catch (Exception x) + { + HandleException("Failed to send the e-mail", x); + } + } + + private void HandleException(string prefix, Exception x) + { + if (Environment.UserInteractive) + { + Console.WriteLine(prefix + ": " + x); + } + else + { + this.eventLog.WriteEntry(prefix + ": " + x, EventLogEntryType.Warning); + } + } + } +} diff --git a/EwsMailDl/EwsMailDl.csproj b/EwsMailDl/EwsMailDl.csproj index 532c9df..9ef0df0 100644 --- a/EwsMailDl/EwsMailDl.csproj +++ b/EwsMailDl/EwsMailDl.csproj @@ -1,133 +1,131 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {EA83B646-A276-44EB-A735-67D61738531B} - Exe - Properties - EwsMailDl - EwsMailDl - v4.0 - - - 512 - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - x86 - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - x86 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - prompt - true - false - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - prompt - true - false - - - EwsMailDl.Program - - - - C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll - - - - - - - - - - - - - - - Component - - - - Component - - - - - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - - - + + + + Debug + x86 + 8.0.30703 + 2.0 + {EA83B646-A276-44EB-A735-67D61738531B} + Exe + Properties + EwsMailDl + EwsMailDl + v4.0 + + + 512 + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + true + false + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + true + false + + + EwsMailDl.Program + + + + C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll + + + + + + + + + + + + + + + + Component + + + + Component + + + + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + \ No newline at end of file diff --git a/EwsMailDl/Program.cs b/EwsMailDl/Program.cs index d89a82d..6a478c2 100644 --- a/EwsMailDl/Program.cs +++ b/EwsMailDl/Program.cs @@ -1,486 +1,529 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.ComponentModel; -using System.Configuration.Install; -using System.Diagnostics; -using System.Net; -using System.Net.Security; -using System.Reflection; -using System.Security.Cryptography.X509Certificates; -using System.ServiceProcess; -using System.Threading; -using Microsoft.Exchange.WebServices.Data; - -namespace EwsMailDl -{ - class Program : ServiceBase - { - private CancellationTokenSource tokenSource = null; - - private IList newEmailIdList = null; - - private BlockingCollection emailIdQueue = null; - - private EmailDownloader downloader = null; - - private Thread downloaderThread = null; - - private StreamingSubscriptionConnection subConn = null; - - private string[] programArgs = null; - - private Settings settings = null; - - private FolderId folderId = null; - - private bool downloadingOld = false; - - static int Main(string[] args) - { - if (Environment.UserInteractive) - { - if (args.Length > 0) - { - if (args[0] == "/i") - { - return InstallService(args); - } - - if (args[0] == "/u") - { - return UninstallService(); - } - } - - new Program().StartMonitor(args); - } - else - { - ServiceBase.Run(new Program(args)); - } - - return 0; - } - - private static int InstallService(string[] args) - { - var service = new Program(); - - try - { - var installerArgs = new string[args.Length]; - - for (var i = 1; i < args.Length; ++i) - { - installerArgs[i - 1] = args[i]; - } - - installerArgs[args.Length - 1] = Assembly.GetExecutingAssembly().Location; - - ManagedInstallerClass.InstallHelper(installerArgs); - } - catch (Exception x) - { - if (x.InnerException != null && x.InnerException.GetType() == typeof(Win32Exception)) - { - Win32Exception wx = (Win32Exception)x.InnerException; - - Console.WriteLine("Error 0x{0:X}: Service already installed!", wx.ErrorCode); - - return wx.ErrorCode; - } - - Console.WriteLine(x.ToString()); - - return -1; - } - - return 0; - } - - private static int UninstallService() - { - var service = new Program(); - - try - { - ManagedInstallerClass.InstallHelper(new string[] { "/u", Assembly.GetExecutingAssembly().Location }); - } - catch (Exception x) - { - if (x.InnerException.GetType() == typeof(Win32Exception)) - { - Win32Exception wx = (Win32Exception)x.InnerException; - - Console.WriteLine("Error 0x{0:X}: Service not installed!", wx.ErrorCode); - - return wx.ErrorCode; - } - else - { - Console.WriteLine(x.ToString()); - - return -1; - } - } - - return 0; - } - - public Program() - { - ServiceName = "EwsMailDl"; - EventLog.Log = "Application"; - CanHandlePowerEvent = false; - CanHandleSessionChangeEvent = false; - CanPauseAndContinue = false; - CanShutdown = false; - CanStop = true; - } - - public Program(string[] args) : this() - { - programArgs = args; - } - - protected override void OnStart(string[] serviceArgs) - { - new Thread(new ThreadStart(() => StartMonitor(serviceArgs))).Start(); - } - - private void StartMonitor(string[] serviceArgs) - { - try - { - settings = new Settings(); - - if (programArgs != null) - { - settings.ReadFromArgs(programArgs); - } - - if (serviceArgs.Length > 0) - { - settings.ReadFromArgs(serviceArgs); - } - - folderId = settings.CreateFolderId(); - } - catch (Exception x) - { - HandleException(x); - return; - } - - programArgs = null; - serviceArgs = null; - - if (Environment.UserInteractive) - { - Console.WriteLine("EwsMailDl"); - Console.WriteLine("--"); - Console.WriteLine(settings); - Console.WriteLine("--"); - } - - ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallback; - - newEmailIdList = new List(); - tokenSource = new CancellationTokenSource(); - emailIdQueue = new BlockingCollection(); - downloader = new EmailDownloader(EventLog, emailIdQueue, tokenSource, settings); - - StreamingSubscription sub = null; - - try - { - sub = settings.CreateExchangeService().SubscribeToStreamingNotifications(new FolderId[] { folderId }, EventType.NewMail); - } - catch (Exception x) - { - HandleException(x); - return; - } - - subConn = new StreamingSubscriptionConnection(sub.Service, settings.Lifetime); - - subConn.OnNotificationEvent += OnNotificationEvent; - subConn.OnSubscriptionError += OnSubscriptionError; - subConn.OnDisconnect += OnDisconnect; - - subConn.AddSubscription(sub); - - OpenSubscription(subConn); - - downloaderThread = new Thread(downloader.Run); - - try - { - DownloadAndDeleteOld(); - - downloaderThread.Start(); - - if (Environment.UserInteractive) - { - downloaderThread.Join(); - } - } - catch (Exception x) - { - HandleException(x); - } - } - - private void DownloadAndDeleteOld(int nextPageOffset = 0) - { - downloadingOld = true; - - if (tokenSource.IsCancellationRequested) - { - return; - } - - var exchangeService = settings.CreateExchangeService(); - var searchFilter = settings.CreateSearchFilter(); - var view = new ItemView(24, nextPageOffset) - { - PropertySet = PropertySet.IdOnly - }; - - view.OrderBy.Add(EmailMessageSchema.DateTimeReceived, SortDirection.Ascending); - - if (Environment.UserInteractive) - { - Console.WriteLine("Searching for {0} old e-mails at offset {1}...", view.PageSize, nextPageOffset); - } - - var results = exchangeService.FindItems(folderId, searchFilter, view); - - if (Environment.UserInteractive) - { - Console.Write("...found {0} old e-mails", results.Items.Count); - - if (results.MoreAvailable) - { - Console.WriteLine(" and more are available ({0} total)...", results.TotalCount); - } - else - { - Console.WriteLine("..."); - } - } - - foreach (var item in results.Items) - { - if (item is EmailMessage) - { - emailIdQueue.Add(item.Id); - } - } - - if (results.NextPageOffset.HasValue) - { - DownloadAndDeleteOld(results.NextPageOffset.Value); - } - else - { - if (newEmailIdList != null) - { - if (newEmailIdList.Count > 0) - { - foreach (var emailId in newEmailIdList) - { - emailIdQueue.Add(emailId); - } - - Console.WriteLine("Enqueued {0} new e-mails!", newEmailIdList.Count); - } - - newEmailIdList = null; - } - - downloadingOld = false; - } - } - - private void HandleException(Exception x) - { - if (Environment.UserInteractive) - { - Console.WriteLine(x); - - Environment.Exit(1); - } - else - { - throw x; - } - } - - protected override void OnStop() - { - if (emailIdQueue != null) - { - emailIdQueue.CompleteAdding(); - } - - if (tokenSource != null) - { - tokenSource.Cancel(false); - } - - if (subConn != null && subConn.IsOpen) - { - try - { - subConn.Close(); - } - catch (Exception) { } - } - - try - { - if (downloaderThread != null) - { - downloaderThread.Join(1337); - } - } - catch (Exception) { } - } - - private void OnNotificationEvent(object sender, NotificationEventArgs args) - { - if (emailIdQueue.IsAddingCompleted) - { - return; - } - - foreach (NotificationEvent notificationEvent in args.Events) - { - if (notificationEvent.EventType == EventType.NewMail && notificationEvent is ItemEvent) - { - var itemId = (notificationEvent as ItemEvent).ItemId; - - if (newEmailIdList == null) - { - emailIdQueue.Add(itemId); - } - else - { - newEmailIdList.Add(itemId); - } - } - } - } - - private void OnSubscriptionError(object sender, SubscriptionErrorEventArgs args) - { - if (args.Exception != null) - { - if (Environment.UserInteractive) - { - Console.WriteLine(args.Exception.Message); - } - else - { - throw args.Exception; - } - } - } - - private void OnDisconnect(object sender, SubscriptionErrorEventArgs args) - { - var message = "Subscription disconnected" - + (args.Exception == null ? " :(" : (": " + args.Exception.Message)); - - if (Environment.UserInteractive) - { - Console.WriteLine(message); - } - else - { - EventLog.WriteEntry(message, EventLogEntryType.Warning); - } - - OpenSubscription(sender as StreamingSubscriptionConnection); - - if (!downloadingOld) - { - DownloadAndDeleteOld(); - } - } - - private void OpenSubscription(StreamingSubscriptionConnection subConn) - { - if (tokenSource.IsCancellationRequested) - { - return; - } - - if (Environment.UserInteractive) - { - Console.WriteLine("Connecting the subscription..."); - } - - try - { - subConn.Open(); - } - catch (Exception x) - { - HandleException(x); - return; - } - - if (subConn.IsOpen) - { - var message = "Subscription connected :)"; - - if (Environment.UserInteractive) - { - Console.WriteLine(message); - } - else - { - EventLog.WriteEntry(message, EventLogEntryType.Information); - } - } - } - - private static bool CertificateValidationCallback( - object sender, - X509Certificate certificate, - X509Chain chain, - SslPolicyErrors sslPolicyErrors) - { - if (sslPolicyErrors == SslPolicyErrors.None) - { - return true; - } - - if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) == 0) - { - return false; - } - - if (chain != null && chain.ChainStatus != null) - { - foreach (X509ChainStatus status in chain.ChainStatus) - { - if ((certificate.Subject == certificate.Issuer) && (status.Status == X509ChainStatusFlags.UntrustedRoot)) - { - continue; - } - - if (status.Status != X509ChainStatusFlags.NoError) - { - return false; - } - } - } - - return true; - } - } -} +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel; +using System.Configuration.Install; +using System.Diagnostics; +using System.Net; +using System.Net.Security; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using System.ServiceProcess; +using System.Threading; +using Microsoft.Exchange.WebServices.Data; + +namespace EwsMailDl +{ + class Program : ServiceBase + { + private CancellationTokenSource tokenSource = null; + + private IList newEmailIdList = null; + + private BlockingCollection emailIdQueue = null; + + private EmailDownloader downloader = null; + + private EmailSender sender = null; + + private Thread downloaderThread = null; + + private Thread senderThread = null; + + private StreamingSubscriptionConnection subConn = null; + + private string[] programArgs = null; + + private Settings settings = null; + + private FolderId folderId = null; + + private bool downloadingOld = false; + + static int Main(string[] args) + { + if (Environment.UserInteractive) + { + if (args.Length > 0) + { + if (args[0] == "/i") + { + return InstallService(args); + } + + if (args[0] == "/u") + { + return UninstallService(); + } + } + + new Program().StartMonitor(args); + } + else + { + ServiceBase.Run(new Program(args)); + } + + return 0; + } + + private static int InstallService(string[] args) + { + var service = new Program(); + + try + { + var installerArgs = new string[args.Length]; + + for (var i = 1; i < args.Length; ++i) + { + installerArgs[i - 1] = args[i]; + } + + installerArgs[args.Length - 1] = Assembly.GetExecutingAssembly().Location; + + ManagedInstallerClass.InstallHelper(installerArgs); + } + catch (Exception x) + { + if (x.InnerException != null && x.InnerException.GetType() == typeof(Win32Exception)) + { + Win32Exception wx = (Win32Exception)x.InnerException; + + Console.WriteLine("Error 0x{0:X}: Service already installed!", wx.ErrorCode); + + return wx.ErrorCode; + } + + Console.WriteLine(x.ToString()); + + return -1; + } + + return 0; + } + + private static int UninstallService() + { + var service = new Program(); + + try + { + ManagedInstallerClass.InstallHelper(new string[] { "/u", Assembly.GetExecutingAssembly().Location }); + } + catch (Exception x) + { + if (x.InnerException.GetType() == typeof(Win32Exception)) + { + Win32Exception wx = (Win32Exception)x.InnerException; + + Console.WriteLine("Error 0x{0:X}: Service not installed!", wx.ErrorCode); + + return wx.ErrorCode; + } + else + { + Console.WriteLine(x.ToString()); + + return -1; + } + } + + return 0; + } + + public Program() + { + ServiceName = "EwsMailDl"; + EventLog.Log = "Application"; + CanHandlePowerEvent = false; + CanHandleSessionChangeEvent = false; + CanPauseAndContinue = false; + CanShutdown = false; + CanStop = true; + } + + public Program(string[] args) : this() + { + programArgs = args; + } + + protected override void OnStart(string[] serviceArgs) + { + new Thread(new ThreadStart(() => StartMonitor(serviceArgs))).Start(); + } + + private void StartMonitor(string[] serviceArgs) + { + try + { + settings = new Settings(); + + if (programArgs != null) + { + settings.ReadFromArgs(programArgs); + } + + if (serviceArgs.Length > 0) + { + settings.ReadFromArgs(serviceArgs); + } + + folderId = settings.CreateFolderId(); + } + catch (Exception x) + { + HandleException(x); + return; + } + + programArgs = null; + serviceArgs = null; + + if (Environment.UserInteractive) + { + Console.WriteLine("EwsMailDl"); + Console.WriteLine("--"); + Console.WriteLine(settings); + Console.WriteLine("--"); + } + + ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallback; + + tokenSource = new CancellationTokenSource(); + + if (!string.IsNullOrWhiteSpace(settings.SavePath)) + { + CreateDownloaderThread(); + } + + if (!string.IsNullOrWhiteSpace(settings.InputPath)) + { + CreateSenderThread(); + } + + try + { + if (downloaderThread != null) + { + DownloadAndDeleteOld(); + downloaderThread.Start(); + } + + if (senderThread != null) + { + senderThread.Start(); + } + + if (Environment.UserInteractive) + { + if (downloaderThread != null) + { + downloaderThread.Join(); + } + + if (senderThread != null) + { + senderThread.Join(); + } + } + } + catch (Exception x) + { + HandleException(x); + } + } + + private void CreateDownloaderThread() + { + newEmailIdList = new List(); + emailIdQueue = new BlockingCollection(); + downloader = new EmailDownloader(EventLog, emailIdQueue, tokenSource, settings); + + StreamingSubscription sub = null; + + try + { + sub = settings.CreateExchangeService().SubscribeToStreamingNotifications(new FolderId[] { folderId }, EventType.NewMail); + } + catch (Exception x) + { + HandleException(x); + } + + subConn = new StreamingSubscriptionConnection(sub.Service, settings.Lifetime); + + subConn.OnNotificationEvent += OnNotificationEvent; + subConn.OnSubscriptionError += OnSubscriptionError; + subConn.OnDisconnect += OnDisconnect; + + subConn.AddSubscription(sub); + + OpenSubscription(subConn); + + downloaderThread = new Thread(downloader.Run); + } + + private void CreateSenderThread() + { + sender = new EmailSender(EventLog, tokenSource, settings); + senderThread = new Thread(sender.Run); + } + + private void DownloadAndDeleteOld(int nextPageOffset = 0) + { + downloadingOld = true; + + if (tokenSource.IsCancellationRequested) + { + return; + } + + var exchangeService = settings.CreateExchangeService(); + var searchFilter = settings.CreateSearchFilter(); + var view = new ItemView(24, nextPageOffset) + { + PropertySet = PropertySet.IdOnly + }; + + view.OrderBy.Add(EmailMessageSchema.DateTimeReceived, SortDirection.Ascending); + + if (Environment.UserInteractive) + { + Console.WriteLine("Searching for {0} old e-mails at offset {1}...", view.PageSize, nextPageOffset); + } + + var results = exchangeService.FindItems(folderId, searchFilter, view); + + if (Environment.UserInteractive) + { + Console.Write("...found {0} old e-mails", results.Items.Count); + + if (results.MoreAvailable) + { + Console.WriteLine(" and more are available ({0} total)...", results.TotalCount); + } + else + { + Console.WriteLine("..."); + } + } + + foreach (var item in results.Items) + { + if (item is EmailMessage) + { + emailIdQueue.Add(item.Id); + } + } + + if (results.NextPageOffset.HasValue) + { + DownloadAndDeleteOld(results.NextPageOffset.Value); + } + else + { + if (newEmailIdList != null) + { + if (newEmailIdList.Count > 0) + { + foreach (var emailId in newEmailIdList) + { + emailIdQueue.Add(emailId); + } + + Console.WriteLine("Enqueued {0} new e-mails!", newEmailIdList.Count); + } + + newEmailIdList = null; + } + + downloadingOld = false; + } + } + + private void HandleException(Exception x) + { + if (Environment.UserInteractive) + { + Console.WriteLine(x); + + Environment.Exit(1); + } + else + { + throw x; + } + } + + protected override void OnStop() + { + if (emailIdQueue != null) + { + emailIdQueue.CompleteAdding(); + } + + if (tokenSource != null) + { + tokenSource.Cancel(false); + } + + if (subConn != null && subConn.IsOpen) + { + try + { + subConn.Close(); + } + catch (Exception) { } + } + + try + { + if (downloaderThread != null) + { + downloaderThread.Join(1337); + } + + if (senderThread != null) + { + senderThread.Join(1337); + } + } + catch (Exception) { } + } + + private void OnNotificationEvent(object sender, NotificationEventArgs args) + { + if (emailIdQueue.IsAddingCompleted) + { + return; + } + + foreach (NotificationEvent notificationEvent in args.Events) + { + if (notificationEvent.EventType == EventType.NewMail && notificationEvent is ItemEvent) + { + var itemId = (notificationEvent as ItemEvent).ItemId; + + if (newEmailIdList == null) + { + emailIdQueue.Add(itemId); + } + else + { + newEmailIdList.Add(itemId); + } + } + } + } + + private void OnSubscriptionError(object sender, SubscriptionErrorEventArgs args) + { + if (args.Exception != null) + { + if (Environment.UserInteractive) + { + Console.WriteLine(args.Exception.Message); + } + else + { + throw args.Exception; + } + } + } + + private void OnDisconnect(object sender, SubscriptionErrorEventArgs args) + { + var message = "Subscription disconnected" + + (args.Exception == null ? " :(" : (": " + args.Exception.Message)); + + if (Environment.UserInteractive) + { + Console.WriteLine(message); + } + else + { + EventLog.WriteEntry(message, EventLogEntryType.Warning); + } + + OpenSubscription(sender as StreamingSubscriptionConnection); + + if (!downloadingOld) + { + DownloadAndDeleteOld(); + } + } + + private void OpenSubscription(StreamingSubscriptionConnection subConn) + { + if (tokenSource.IsCancellationRequested) + { + return; + } + + if (Environment.UserInteractive) + { + Console.WriteLine("Connecting the subscription..."); + } + + try + { + subConn.Open(); + } + catch (Exception x) + { + HandleException(x); + return; + } + + if (subConn.IsOpen) + { + var message = "Subscription connected :)"; + + if (Environment.UserInteractive) + { + Console.WriteLine(message); + } + else + { + EventLog.WriteEntry(message, EventLogEntryType.Information); + } + } + } + + private static bool CertificateValidationCallback( + object sender, + X509Certificate certificate, + X509Chain chain, + SslPolicyErrors sslPolicyErrors) + { + if (sslPolicyErrors == SslPolicyErrors.None) + { + return true; + } + + if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) == 0) + { + return false; + } + + if (chain != null && chain.ChainStatus != null) + { + foreach (X509ChainStatus status in chain.ChainStatus) + { + if ((certificate.Subject == certificate.Issuer) && (status.Status == X509ChainStatusFlags.UntrustedRoot)) + { + continue; + } + + if (status.Status != X509ChainStatusFlags.NoError) + { + return false; + } + } + } + + return true; + } + } +} diff --git a/EwsMailDl/Properties/AssemblyInfo.cs b/EwsMailDl/Properties/AssemblyInfo.cs index 8902d7a..458fcbf 100644 --- a/EwsMailDl/Properties/AssemblyInfo.cs +++ b/EwsMailDl/Properties/AssemblyInfo.cs @@ -1,36 +1,36 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("EwsMailDl")] -[assembly: AssemblyDescription("Exchange E-mail Attachment Downloader")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Łukasz Walukiewicz")] -[assembly: AssemblyProduct("EwsMailDl")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("2d811a21-7db3-4c1c-a7cf-2118d020c834")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.0.0")] -[assembly: AssemblyFileVersion("0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("EwsMailDl")] +[assembly: AssemblyDescription("Exchange E-mail Attachment Downloader")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Łukasz Walukiewicz")] +[assembly: AssemblyProduct("EwsMailDl")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2d811a21-7db3-4c1c-a7cf-2118d020c834")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.0.0")] +[assembly: AssemblyFileVersion("0.0.0")] diff --git a/EwsMailDl/ServiceInstaller.cs b/EwsMailDl/ServiceInstaller.cs index fb43ebc..9d30d47 100644 --- a/EwsMailDl/ServiceInstaller.cs +++ b/EwsMailDl/ServiceInstaller.cs @@ -1,56 +1,56 @@ -using System.ComponentModel; -using System.Configuration.Install; -using System.ServiceProcess; -using System.Text; -using Microsoft.Win32; - -namespace EwsMailDl -{ - [RunInstaller(true)] - public class ProgramInstaller : Installer - { - private ServiceProcessInstaller processInstaller; - private ServiceInstaller serviceInstaller; - - public ProgramInstaller() - { - processInstaller = new ServiceProcessInstaller(); - serviceInstaller = new ServiceInstaller(); - - processInstaller.Account = ServiceAccount.LocalSystem; - serviceInstaller.StartType = ServiceStartMode.Automatic; - serviceInstaller.ServiceName = "EwsMailDl"; - serviceInstaller.DisplayName = "EwsMailDl"; - serviceInstaller.Description = "Monitors and downloads matching e-mail attachments arriving to the specified Exchange account."; - - Installers.Add(serviceInstaller); - Installers.Add(processInstaller); - - processInstaller.AfterInstall += OnAfterInstall; - } - - protected void OnAfterInstall(object sender, InstallEventArgs args) - { - var cmd = new StringBuilder(); - - foreach (string key in Context.Parameters.Keys) - { - if (key == "logtoconsole" || key == "assemblypath" || key == "logfile") - { - continue; - } - - cmd.AppendFormat(" /{0}=\"{1}\"", key, Context.Parameters[key]); - } - - var keyName = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\" + serviceInstaller.ServiceName; - var valueName = "ImagePath"; - var value = Registry.GetValue(keyName, "ImagePath", null); - - if (value != null) - { - Registry.SetValue(keyName, valueName, (value as string) + cmd.ToString(), RegistryValueKind.ExpandString); - } - } - } -} +using System.ComponentModel; +using System.Configuration.Install; +using System.ServiceProcess; +using System.Text; +using Microsoft.Win32; + +namespace EwsMailDl +{ + [RunInstaller(true)] + public class ProgramInstaller : Installer + { + private ServiceProcessInstaller processInstaller; + private ServiceInstaller serviceInstaller; + + public ProgramInstaller() + { + processInstaller = new ServiceProcessInstaller(); + serviceInstaller = new ServiceInstaller(); + + processInstaller.Account = ServiceAccount.LocalSystem; + serviceInstaller.StartType = ServiceStartMode.Automatic; + serviceInstaller.ServiceName = "EwsMailDl"; + serviceInstaller.DisplayName = "EwsMailDl"; + serviceInstaller.Description = "Monitors and downloads matching e-mail attachments arriving to the specified Exchange account."; + + Installers.Add(serviceInstaller); + Installers.Add(processInstaller); + + processInstaller.AfterInstall += OnAfterInstall; + } + + protected void OnAfterInstall(object sender, InstallEventArgs args) + { + var cmd = new StringBuilder(); + + foreach (string key in Context.Parameters.Keys) + { + if (key == "logtoconsole" || key == "assemblypath" || key == "logfile") + { + continue; + } + + cmd.AppendFormat(" /{0}=\"{1}\"", key, Context.Parameters[key]); + } + + var keyName = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\" + serviceInstaller.ServiceName; + var valueName = "ImagePath"; + var value = Registry.GetValue(keyName, "ImagePath", null); + + if (value != null) + { + Registry.SetValue(keyName, valueName, (value as string) + cmd.ToString(), RegistryValueKind.ExpandString); + } + } + } +} diff --git a/EwsMailDl/Settings.cs b/EwsMailDl/Settings.cs index eb97eb6..e243c77 100644 --- a/EwsMailDl/Settings.cs +++ b/EwsMailDl/Settings.cs @@ -1,186 +1,195 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Configuration.Install; -using System.Text; -using Microsoft.Exchange.WebServices.Data; - -namespace EwsMailDl -{ - class Settings - { - public ExchangeVersion Version { get; set; } - - public Uri Url { get; set; } - - public string Username { get; set; } - - public string Password { get; set; } - - public int Lifetime - { - get { return _lifetime; } - set { _lifetime = value < 1 ? 1 : value > 30 ? 30 : value; } - } - - public string SavePath { get; set; } - - public string FolderName { get; set; } - - public FolderId FolderId { get; set; } - - public IList SubjectFilters { get; set; } - - public bool Timestamp { get; set; } - - private int _lifetime = 30; - - public Settings() - { - Version = ExchangeVersion.Exchange2010_SP2; - Url = new Uri("https://localhost/EWS/Exchange.asmx"); - Username = "someone@localhost"; - Password = "T0PS3CR3T"; - SavePath = Environment.CurrentDirectory; - FolderName = "Inbox"; - FolderId = null; - SubjectFilters = new List(); - Timestamp = false; - } - - public void ReadFromArgs(string[] args) - { - var context = new InstallContext(null, args); - - foreach (DictionaryEntry param in context.Parameters) - { - string argName = param.Key as string; - string argValue = param.Value as string; - - if (argName == null || argValue == null) - { - continue; - } - - switch (argName) - { - case "version": - Version = (ExchangeVersion)Enum.Parse(typeof(ExchangeVersion), argValue, true); - break; - - case "url": - Url = new Uri(argValue); - break; - - case "username": - Username = argValue; - break; - - case "password": - Password = argValue; - break; - - case "lifetime": - Lifetime = Int32.Parse(argValue); - break; - - case "foldername": - FolderName = argValue; - break; - - case "folderid": - FolderId = new FolderId(argValue); - break; - - case "savepath": - SavePath = argValue; - break; - - case "subject": - SubjectFilters.Add(argValue); - break; - - case "timestamp": - Timestamp = true; - break; - } - } - } - - public override string ToString() - { - var sb = new StringBuilder(); - - sb.AppendFormat("Version : {0}", Version); - sb.AppendLine(); - sb.AppendFormat("URL : {0}", Url); - sb.AppendLine(); - sb.AppendFormat("Username : {0}", Username); - sb.AppendLine(); - sb.AppendFormat("Lifetime : {0}", Lifetime); - sb.AppendLine(); - sb.AppendFormat("Folder name: {0}", FolderName); - sb.AppendLine(); - sb.AppendFormat("Folder ID : {0}", FolderId); - sb.AppendLine(); - sb.AppendFormat("Save path : {0}", SavePath); - sb.AppendLine(); - sb.AppendFormat("Timestamp : {0}", Timestamp ? "Yes" : "No"); - sb.AppendLine(); - sb.AppendFormat("Subject : {0}", String.Join(" OR ", SubjectFilters)); - - return sb.ToString(); - } - - public FolderId CreateFolderId() - { - if (FolderId != null) - { - return FolderId; - } - - WellKnownFolderName folderName; - - if (Enum.TryParse(FolderName, true, out folderName)) - { - return new FolderId(folderName); - } - - var results = CreateExchangeService().FindFolders( - WellKnownFolderName.Root, - new SearchFilter.IsEqualTo(FolderSchema.DisplayName, FolderName), - new FolderView(1) { PropertySet = PropertySet.IdOnly, Traversal = FolderTraversal.Deep } - ); - - return results.Folders.Count > 0 ? results.Folders[0].Id : null; - } - - public SearchFilter CreateSearchFilter() - { - var hasAttachments = new SearchFilter.IsEqualTo(ItemSchema.HasAttachments, true); - - if (SubjectFilters.Count == 0) - { - return hasAttachments; - } - - var subjectFilters = new SearchFilter.SearchFilterCollection(LogicalOperator.Or); - - foreach (var subjectFilter in SubjectFilters) - { - subjectFilters.Add(new SearchFilter.ContainsSubstring(ItemSchema.Subject, subjectFilter, ContainmentMode.Substring, ComparisonMode.Exact)); - } - - return new SearchFilter.SearchFilterCollection(LogicalOperator.And, hasAttachments, subjectFilters); - } - - public ExchangeService CreateExchangeService() - { - return new ExchangeService(Version) - { - Credentials = new WebCredentials(Username, Password), - Url = Url - }; - } - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Configuration.Install; +using System.Text; +using Microsoft.Exchange.WebServices.Data; + +namespace EwsMailDl +{ + class Settings + { + public ExchangeVersion Version { get; set; } + + public Uri Url { get; set; } + + public string Username { get; set; } + + public string Password { get; set; } + + public int Lifetime + { + get { return _lifetime; } + set { _lifetime = value < 1 ? 1 : value > 30 ? 30 : value; } + } + + public string SavePath { get; set; } + + public string InputPath { get; set; } + + public string FolderName { get; set; } + + public FolderId FolderId { get; set; } + + public IList SubjectFilters { get; set; } + + public bool Timestamp { get; set; } + + private int _lifetime = 30; + + public Settings() + { + Version = ExchangeVersion.Exchange2010_SP2; + Url = new Uri("https://localhost/EWS/Exchange.asmx"); + Username = "someone@localhost"; + Password = "T0PS3CR3T"; + SavePath = ""; + InputPath = ""; + FolderName = "Inbox"; + FolderId = null; + SubjectFilters = new List(); + Timestamp = false; + } + + public void ReadFromArgs(string[] args) + { + var context = new InstallContext(null, args); + + foreach (DictionaryEntry param in context.Parameters) + { + string argName = param.Key as string; + string argValue = param.Value as string; + + if (argName == null || argValue == null) + { + continue; + } + + switch (argName) + { + case "version": + Version = (ExchangeVersion)Enum.Parse(typeof(ExchangeVersion), argValue, true); + break; + + case "url": + Url = new Uri(argValue); + break; + + case "username": + Username = argValue; + break; + + case "password": + Password = argValue; + break; + + case "lifetime": + Lifetime = Int32.Parse(argValue); + break; + + case "foldername": + FolderName = argValue; + break; + + case "folderid": + FolderId = new FolderId(argValue); + break; + + case "savepath": + SavePath = argValue; + break; + + case "inputpath": + InputPath = argValue; + break; + + case "subject": + SubjectFilters.Add(argValue); + break; + + case "timestamp": + Timestamp = true; + break; + } + } + } + + public override string ToString() + { + var sb = new StringBuilder(); + + sb.AppendFormat("Version : {0}", Version); + sb.AppendLine(); + sb.AppendFormat("URL : {0}", Url); + sb.AppendLine(); + sb.AppendFormat("Username : {0}", Username); + sb.AppendLine(); + sb.AppendFormat("Lifetime : {0}", Lifetime); + sb.AppendLine(); + sb.AppendFormat("Folder name: {0}", FolderName); + sb.AppendLine(); + sb.AppendFormat("Folder ID : {0}", FolderId); + sb.AppendLine(); + sb.AppendFormat("Save path : {0}", SavePath); + sb.AppendLine(); + sb.AppendFormat("Input path : {0}", InputPath); + sb.AppendLine(); + sb.AppendFormat("Timestamp : {0}", Timestamp ? "Yes" : "No"); + sb.AppendLine(); + sb.AppendFormat("Subject : {0}", String.Join(" OR ", SubjectFilters)); + + return sb.ToString(); + } + + public FolderId CreateFolderId() + { + if (FolderId != null) + { + return FolderId; + } + + WellKnownFolderName folderName; + + if (Enum.TryParse(FolderName, true, out folderName)) + { + return new FolderId(folderName); + } + + var results = CreateExchangeService().FindFolders( + WellKnownFolderName.Root, + new SearchFilter.IsEqualTo(FolderSchema.DisplayName, FolderName), + new FolderView(1) { PropertySet = PropertySet.IdOnly, Traversal = FolderTraversal.Deep } + ); + + return results.Folders.Count > 0 ? results.Folders[0].Id : null; + } + + public SearchFilter CreateSearchFilter() + { + var hasAttachments = new SearchFilter.IsEqualTo(ItemSchema.HasAttachments, true); + + if (SubjectFilters.Count == 0) + { + return hasAttachments; + } + + var subjectFilters = new SearchFilter.SearchFilterCollection(LogicalOperator.Or); + + foreach (var subjectFilter in SubjectFilters) + { + subjectFilters.Add(new SearchFilter.ContainsSubstring(ItemSchema.Subject, subjectFilter, ContainmentMode.Substring, ComparisonMode.Exact)); + } + + return new SearchFilter.SearchFilterCollection(LogicalOperator.And, hasAttachments, subjectFilters); + } + + public ExchangeService CreateExchangeService() + { + return new ExchangeService(Version) + { + Credentials = new WebCredentials(Username, Password), + Url = Url + }; + } + } +} diff --git a/license.md b/license.md index a830971..78bf192 100644 --- a/license.md +++ b/license.md @@ -1,20 +1,20 @@ -Copyright (c) 2014 Łukasz Walukiewicz - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2014 Łukasz Walukiewicz + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/readme.md b/readme.md index 9e5cce5..a4a8fa5 100644 --- a/readme.md +++ b/readme.md @@ -1,140 +1,204 @@ -# EwsMailDl - -Microsoft Exchange E-mail Attachment Downloader. - -1. Connects to the specified Exchange server. -2. Subscribes to streaming notifications of type `NewMail` in the specified folder. -3. Downloads attachments from all existing e-mails (e-mails with attachments - and subjects matching the specified filters). -4. Downloads attachments from any new, matching e-mails. -5. If the subscription closes, repeat from step 3. -6. If any error occurs, crash (Windows Service Recovery should take care - of restarting the service). - -After downloading all attachments from the matched e-mail, the e-mail is deleted. - -Tested with Exchange server version 2010 SP2. - -## Requirements - -### .NET Framework - - * __Version__: 4.x - * __Website__: http://www.microsoft.com/net - * __Download__: http://www.microsoft.com/net/downloads - -### EWS Managed API - - * __Version__: 2.x - * __Website__: http://msdn.microsoft.com/en-us/library/dd633709(v=exchg.80).aspx - * __Download__: http://www.microsoft.com/en-us/download/details.aspx?id=35371 - -## Usage - -EwsMailDl can be run as a console application or a service application. - -### Console - -To run EwsMailDl as a console application, execute the following command: - -``` -EwsMailDl.exe -``` - -where `` is a list of the [configuration arguments](#configuration). - -This mode is intended for testing purposes only. - -### Service - -To install EwsMailDl as a service, execute the following command: - -``` -EwsMailDl.exe /i -``` - -where `` is a list of the [configuration arguments](#configuration). - -The serivce can be then started using the standard `net start` or `sc start` -commands. - -This mode is intended for use in production. The created `EwsMailDl` service -should be configured to restart on failure (*Recovery* tab in the service's -properties), because it will crash on any error. - -To uninstall the service, execute the following command: - -``` -EwsMailDl.exe /u -``` - -### Configuration - -Configuration arguments are specified in the following format: - -``` -/="" /="" ... -``` - -for example: - -``` -/quas="q" /wex="w" /exort="e" -``` - -Available configuration arguments are: - - * `version` - a version of the Exchange server we are connecting to. - Valid values are: `Exchange2010_SP1`, `Exchange2010_SP2` or `Exchange2013`. - Defaults to `Exchange2010_SP2`. - - * `url` - an URL to the server's EWS. For example, if the server we're trying - to connect to is `mail.example.com`, then the EWS URL should be: - `https://mail.example.com/EWS/Exchange.asmx`. - - * `username` - a username of the e-mail account we're trying to connect to. - - * `password` - a password of the e-mail account we're trying to connect to. - - * `lifetime` - a number of minutes (between 1 and 30) the subscription - notification is active on the server. Defaults to 30 minutes. - - * `folderName` - a name of the folder in the user's account we're going to - be monitoring for e-mails. Can be a `WellKnownFolderName` or any other - user-created folder. Defaults to `Inbox`. - - * `folderId` - an ID of the folder in the user's account we're going to be - monitoring for e-mails. Optional. If specified, the `folderName` is not used. - - * `savePath` - a path to a folder where the attachments should be downloaded to. - Defaults to the current directory. - - * `subject` - a filter for the e-mails to download. Only e-mails with a subject - containing the specified string will be taken into consideration (they must - have attachments too). - Can be specified multiple times. - Multiple filters are concatenated using `OR`. - - * `timestamp` - determines whether to prepend `@` to - the downloaded attachment file names, where `` is the e-mail's - date received as a UNIX timestamp. For example, attachment named `Test.html` - that arrived at 2014-01-02 12:00:00 GMT will be saved as `1388664000000@Test.html`. - -#### Example - -``` -EwsMailDl.exe ^ - /url="http://mail.example.com/EWS/Exchange.asmx" ^ - /username="code1\foobar" ^ - /password="top $$$ecret" ^ - /folderName="Baz" ^ - /savePath="C:/attachments" ^ - /subject="FOO" ^ - /subject="BAR" ^ - /timestamp="1" -``` - -## License - -This project is released under the -[MIT License](https://raw.github.com/morkai/EwsMailDl/master/license.md). +# EwsMailDl + +Microsoft Exchange E-mail Sender and Attachment Downloader. + +Tested with Exchange server version 2010 SP2. + +## Attachment Downloader + +1. Connects to the specified Exchange server. +2. Subscribes to streaming notifications of type `NewMail` in the specified folder. +3. Downloads attachments from all existing e-mails (e-mails with attachments + and subjects matching the specified filters). +4. Downloads attachments from any new, matching e-mails. +5. If the subscription closes, repeat from step 3. +6. If any error occurs, crash (Windows Service Recovery should take care + of restarting the service). + +After all attachments are downloaded, the e-mail is deleted. + +## E-mail Sender + +1. Monitors the specified input path for files matching the `*.email` pattern. +2. Reads, parses and sends e-mail based on the file contents. + +After the e-mail is sent, the file is deleted. + +The `.email` file should have the following format: + +``` +: +Body: + +``` + +Available headers are: + + * `Subject` - sets the `EmailMessage.Subject` property. + + * `Importance` - sets the `EmailMessage.Importance` property (`low`, `normal` or `high`). + + * `To`, `ToRecipients` - adds recipients to the `EmailMessage.ToRecipients` list. + Multiple recipients should be separated by a comma. + + * `Cc`, `CcRecipients` - adds recipients to the `EmailMessage.CcRecipients` list. + Multiple recipients should be separated by a comma. + + * `Bcc`, `BccRecipients` - adds recipients to the `EmailMessage.BccRecipients` list. + Multiple recipients should be separated by a comma. + + * `From` - sets the `EmailMessage.From` property. + + * `ReplyTo` - sets the `EmailMessage.ReplyTo` property. + + * `Html` - `1` or `true` if the e-mail body is HTML. If not specified, the body will still + be sent as HTML but all new lines `\n` will be replaced with `
` and spaces ` ` at + the beginning of each line will be replaced with ` `. + +## Requirements + +### .NET Framework + + * __Version__: 4.x + * __Website__: http://www.microsoft.com/net + * __Download__: http://www.microsoft.com/net/downloads + +### EWS Managed API + + * __Version__: 2.x + * __Website__: http://msdn.microsoft.com/en-us/library/dd633709(v=exchg.80).aspx + * __Download__: http://www.microsoft.com/en-us/download/details.aspx?id=35371 + +## Usage + +EwsMailDl can be run as a console application or a service application. + +### Console + +To run EwsMailDl as a console application, execute the following command: + +``` +EwsMailDl.exe +``` + +where `` is a list of the [configuration arguments](#configuration). + +This mode is intended for testing purposes only. + +### Service + +To install EwsMailDl as a service, execute the following command: + +``` +EwsMailDl.exe /i +``` + +where `` is a list of the [configuration arguments](#configuration). + +The serivce can be then started using the standard `net start` or `sc start` +commands. + +This mode is intended for use in production. The created `EwsMailDl` service +should be configured to restart on failure (*Recovery* tab in the service's +properties), because it will crash on any error. + +To uninstall the service, execute the following command: + +``` +EwsMailDl.exe /u +``` + +### Configuration + +Configuration arguments are specified in the following format: + +``` +/="" /="" ... +``` + +for example: + +``` +/quas="q" /wex="w" /exort="e" +``` + +Available configuration arguments are: + + * `version` - a version of the Exchange server we are connecting to. + Valid values are: `Exchange2010_SP1`, `Exchange2010_SP2` or `Exchange2013`. + Defaults to `Exchange2010_SP2`. + + * `url` - an URL to the server's EWS. For example, if the server we're trying + to connect to is `mail.example.com`, then the EWS URL should be: + `https://mail.example.com/EWS/Exchange.asmx`. + + * `username` - a username of the e-mail account we're trying to connect to. + + * `password` - a password of the e-mail account we're trying to connect to. + + * `lifetime` - a number of minutes (between 1 and 30) the subscription + notification is active on the server. Defaults to 30 minutes. + + * `folderName` - a name of the folder in the user's account we're going to + be monitoring for e-mails. Can be a `WellKnownFolderName` or any other + user-created folder. Defaults to `Inbox`. + + * `folderId` - an ID of the folder in the user's account we're going to be + monitoring for e-mails. Optional. If specified, the `folderName` is not used. + + * `inputPath` - a path to a folder that should be monitored for `.email` files. + + * `savePath` - a path to a folder where the attachments should be downloaded to. + Defaults to the current directory. + + * `subject` - a filter for the e-mails to download. Only e-mails with a subject + containing the specified string will be taken into consideration (they must + have attachments too). + Can be specified multiple times. + Multiple filters are concatenated using `OR`. + + * `timestamp` - determines whether to prepend `@` to + the downloaded attachment file names, where `` is the e-mail's + date received as a UNIX timestamp. For example, attachment named `Test.html` + that arrived at 2014-01-02 12:00:00 GMT will be saved as `1388664000000@Test.html`. + +#### Example + +Running from console: + +``` +EwsMailDl.exe ^ + /url="http://mail.example.com/EWS/Exchange.asmx" ^ + /username="code1\foobar" ^ + /password="top $$$ecret" ^ + /folderName="Baz" ^ + /inputPath="C:/emails" ^ + /savePath="C:/attachments" ^ + /subject="FOO" ^ + /subject="BAR" ^ + /timestamp="1" +``` + +Sending a text e-mail: + +``` +Subject: Test text e-mail +To: someone@the.net +Body: +Hello World! +``` + +Sending an HTML e-mail: + +``` +Subject: Test HTML e-mail +To: someone@the.net +Html: 1 +Body: +

Hello World!

+``` + +## License + +This project is released under the +[MIT License](https://raw.github.com/morkai/EwsMailDl/master/license.md).