DEAK Software

Using POP3 in PowerShell

Dominik Deák

1 Introduction

Previously, we covered techniques for sending messages in PowerShell. This leaves us the problem of being able to receive email in PowerShell. Ultimately, the goal here is to perform POP3 (Post Office Protocol 3) communications [18] without installing an email application, and only use lightweight libraries to implement the following features:

This article will address some of these requirements in detail. Of course, source code will be made available in the Appendix section.

2 Receiving Email in PowerShell

PowerShell offers direct support for email dispatching through the Send-MailMessage command [13]. Alternatively, we can also tap into the .NET Frameworks and send messages with the System.Net.Mail.SmtpClient class [7]. Unfortunately, PowerShell does not feature complementary commands for fetching messages through the POP3 protocol. Nor does it offer IMAP support. Therefore, we will be looking at a third-party solution for fetching emails, such as the OpenPOP.NET library [1, 2].

The official OpenPOP.NET website provides a comprehensive documentation for the library [11], and also a number of useful examples [12]. Translating some of those examples to PowerShell should be relatively easy. In the next few sections we will explore the following OpenPOP.NET classes:

We will also briefly look at the System.Net.Mail classes listed below [14].

2.1 Connecting to the POP3 Server

In order to communicate with the POP3 server, we need to construct a Pop3Client class, then initialise it with the following information: the POP3 server and port, credentials for authentication, and indicate whether to use SSL cryptographic protocols for communication. We can encapsulate all of those requirements into a convenient utility function, called makePOP3Client:

function makePOP3Client
   {
   Param
      (
      [string] $server,
      [int] $port,
      [bool] $enableSSL,
      [string] $username,
      [string] $password
      )

   $pop3Client = New-Object OpenPop.Pop3.Pop3Client

   $pop3Client.connect( $server, $port, $enableSSL )

   if ( !$pop3Client.connected )
      {
      throw "Unable to create POP3 client. Connection failed with server $server"
      }

   $pop3Client.authenticate( $username, $password )

   return $pop3Client
   }

Most POP3 servers expect the connection to be secured with cryptographic protocols, such as the Secure Sockets Layer (SSL). Typically, POP3 servers open a dedicated port for encrypted traffic, as shown in the table below. However, always check what ports are available with your email service provider.

Cryptographic Protocol $port $enableSSL
None 110 $false
Secure Sockets Layer (SSL) 995 $true

The makePOP3Client function will throw an exception if the connection fails. Your code should handle such exceptions gracefully. Once a connection is established with the POP3 server, the Pop3Client instance can be reused to fetch multiple messages in a single batch operation.

2.2 Fetching Messages

The simple example below demonstrates how to fetch messages from a POP3 server. The fetchAndListMessages function initially queries Pop3Client.GetMessageCount to see how many messages are available on the server. Only the first $count messages are fetched from the server in reversed order, because most servers give the latest message the highest index. Note that the available messages are indexed in the range of [1, $messageCount] inclusive. We are not doing much with the messages at this stage, only display some of their header data in the console.

function fetchAndListMessages
   {
   Param
      (
      [OpenPop.Pop3.Pop3Client] $pop3Client,
      [int] $count
      )

   $messageCount = $pop3Client.getMessageCount()

   Write-Host "Messages available:" $messageCount

   $messagesEnd = [math]::max( $messageCount - $count, 0 )

   Write-Host "Fetching messages" $messageCount "to" ($messagesEnd + 1) "..."

   for ( $messageIndex = $messageCount; $messageIndex -gt $messagesEnd; $messageIndex-- )
      {
      $incomingMessage = $pop3Client.getMessage( $messageIndex )

      Write-Host "Message $messageIndex`:"
      Write-Host "`tFrom:" $incomingMessage.headers.from
      Write-Host "`tSubject:" $incomingMessage.headers.subject
      Write-Host "`tDate:" $incomingMessage.headers.dateSent
      }
   }

2.3 Saving Messages to a File

Saving messages is actually very easy. OpenPOP.NET provides a convenience function Message.Save that allows us to serialise the entire Message class contents into an IO stream. The saveMessage utility function has all the necessary boilerplate code to stream a Message class to file:

function saveMessage
   {
   Param
      (
      [OpenPop.Mime.Message] $incomingMessage,
      [string] $outURL
      )

   New-Item -Path $outURL -ItemType "File" -Force | Out-Null

   $outStream = New-Object IO.FileStream $outURL, "Create"

   $incomingMessage.save( $outStream )

   $outStream.close()
   }

The example below demonstrates how the saveMessage utility function is used to dump a bunch of emails to files. The fetchAndSaveMessages is similar to our earlier example (fetchAndListMessages), except this time we are saving the first $count messages to a temporary location, as specified by the path $tempURL. Unique file names are generated from the UID associated with each message. The fetched messages are then saved at their respective unique file locations.

function fetchAndSaveMessages
   {
   Param
      (
      [OpenPop.Pop3.Pop3Client] $pop3Client,
      [int] $count,
      [string] $tempURL
      )

   $messageCount = $pop3Client.getMessageCount()

   Write-Host "Messages available:" $messageCount

   $messagesEnd = [math]::max( $messageCount - $count, 0 )

   Write-Host "Saving messages" $messageCount "to" ($messagesEnd + 1) "..."

   for ( $messageIndex = $messageCount; $messageIndex -gt $messagesEnd; $messageIndex-- )
      {
      $uid = $pop3Client.getMessageUid( $messageIndex )

      $emailURL = Join-Path -Path $tempURL -ChildPath ($uid + ".eml")

      Write-Host "Saving message $messageIndex to:" $emailURL

      $incomingMessage = $pop3Client.getMessage( $messageIndex )

      saveMessage $incomingMessage $emailURL
      }
   }

2.4 Saving Attachments to a File

In this section we will demonstrate the following:

  1. How to convert a OpenPop.Mime.Message object into a System.Net.Mail.MailMessage object; and
  2. How to save a System.Net.Mail.Attachment object to file.

The latter task is performed by the saveAttachment function, as demonstrated in the code snippet below. All we're doing here is create an output file stream, at the location specified by $outURL, and then we stream the attachment content to it.

function saveAttachment
   {
   Param
      (
      [System.Net.Mail.Attachment] $attachment,
      [string] $outURL
      )

   New-Item -Path $outURL -ItemType "File" -Force | Out-Null

   $outStream = New-Object IO.FileStream $outURL, "Create"

   $attachment.contentStream.copyTo( $outStream )

   $outStream.close()
   }

OpenPOP.NET implements yet another convenience function, called Message.ToMailMessage, that allows us to convert a OpenPop.Mime.Message object directly into a System.Net.Mail.MailMessage object. Such conversions are useful when we want to forward messages using the SmtpClient class via the SMTP protocol [7, 19].

Side note: When forwarding messages, make sure the To and From address fields are correct. See the article, Using SMTP in PowerShell, for some examples. Converting to System.Net.Mail.MailMessage objects will have also have some limitations, as some metadata will not be copied over. See the OpenPOP.NET documentation for details [16].

The System.Net.Mail.MailMessage class is also much nicer to work with (a subjective opinion), because it implements properties that allows us to modify the message, or enumerate attachments with minimal code, as demonstrated in the fetchAndSaveAttachments function below.

The following example code simply dumps the attachments (if any) for the first $count messages fetched from the server. The file name of each attachment is constructed from the combination of the email's UID and the actual attachment name embedded in the message. We do this to prevent file name collisions between attachments that have identical name metadata.

function fetchAndSaveAttachments
   {
   Param
      (
      [OpenPop.Pop3.Pop3Client] $pop3Client,
      [int] $count,
      [string] $tempURL
      )

   $messageCount = $pop3Client.getMessageCount()

   Write-Host "Messages available:" $messageCount

   $messagesEnd = [math]::max( $messageCount - $count, 0 )

   Write-Host "Saving attachments for messages" $messageCount "to" ($messagesEnd + 1) "..."

   for ( $messageIndex = $messageCount; $messageIndex -gt $messagesEnd; $messageIndex-- )
      {
      $uid = $pop3Client.getMessageUid( $messageIndex )

      $incomingMessage = $pop3Client.getMessage( $messageIndex ).toMailMessage()

      Write-Host "Processing message $messageIndex..."

      foreach ( $attachment in $incomingMessage.attachments )
         {
         $attachmentURL = Join-Path -Path $tempURL -ChildPath $uid | Join-Path -ChildPath $attachment.name

         Write-Host "`tSaving attachment to:" $attachmentURL

         saveAttachment $attachment $attachmentURL
         }
      }
   }

2.5 Loading a Message from a File

OpenPOP.NET will load and save email files in the RFC-822 file format [15]. Emails can be read from a file stream using the static method Message::Load. The loadMessage function in the snippet below opens a file stream at $inURL, reads its content, then returns the OpenPop.Mime.Message object.

function loadMessage
   {
   Param ( [string] $inURL )

   $inStream = New-Object IO.FileStream $inURL, "Open"

   $message = [OpenPop.Mime.Message]::load( $inStream )

   $inStream.close()

   return $message
   }

To debug the contents of an email file, we can use the loadAndListMessage function, as shown in the next example. Just like before, we are using Message.ToMailMessage to convert a OpenPop.Mime.Message object directly into a System.Net.Mail.MailMessage object, which is then used for displaying the email contents in the console.

function loadAndListMessage
   {
   Param ( [string] $inURL )

   Write-Host "`Loading message from:" $inURL

   $message = ( loadMessage $inURL ).toMailMessage()

   Write-Host "`tFrom:" $message.from
   Write-Host "`tTo:" $message.to

   if ( $message.cc )
      {
      Write-Host "`tCC:" $message.cc
      }

   if ( $message.bcc )
      {
      Write-Host "`tBCC:" $message.bcc
      }

   if ( $message.replyToList )
      {
      Write-Host "`tReply To:" $message.replyToList
      }

   if ( $message.subject )
      {
      Write-Host "`tSubject:" $message.subject
      }

   if ( $message.body )
      {
      Write-Host "`tBody:" $message.body
      }
   }

2.6 Deleting Messages

OpenPOP.NET can also delete messages from the POP3 server. You can use the function Pop3Client.DeleteMessage to mark a particular message for deletion. Alternatively, one can also take the scorched earth approach, and use the Pop3Client.DeleteAllMessages function to clear out the entire inbox. In either case, the messages will not be deleted until a QUIT command is sent to the POP3 server. This is done when you call Pop3Client.Disconnect manually, or when the Pop3Client object is disposed.

Warning: Deleting emails is a dangerous operation. In most cases, messages will be removed irreversibly. Therefore, consider setting up a toy email account for development and testing purposes. Never play around with this feature on a production email account.

2.7 Complete Demo

The code below consolidates all the different examples discussed in the previous sections. We are going to use a hypothetical Gmail account (recipient.example@gmail.com) as the source for the messages. Communications between our script and the server will be conducted over SSL encryption.

Note how we are using Reflection.Assembly library to manually load the OpenPOP.NET binary file from the specified location. This allows PowerShell to bind and work with external .NET Framework classes and assemblies. Once the library is loaded, we can connect to the POP3 server and run all the example functions. We manually call the Pop3Client.Disconnect function at the end, which dispatches a QUIT command to the POP3 server. Hypothetically, messages pending for deletion would be applied at this point; although the Pop3Client destructor can also do this for us automatically.

As a final note, deleting sensitive data with the Remove-Variable command is also good practice. Typically you'd want to delete variables that was previously used for containing user credentials, or server connection state, and so forth. For the extra paranoid, consider using Clear-Variable before Remove-Variable.

# Path configurations
$openPopLibraryURL = $ExecutionContext.SessionState.Path
   .GetUnresolvedProviderPathFromPSPath( "..\Binaries\OpenPop.dll" )
$tempBaseURL = $ExecutionContext.SessionState.Path
   .GetUnresolvedProviderPathFromPSPath( "..\Temp\" )
$testMessageURL = $ExecutionContext.SessionState.Path
   .GetUnresolvedProviderPathFromPSPath( "..\Examples\Test-Message.eml" )

# Incoming email configuration used for fetching messages
$incomingUsername  = "recipient.example@gmail.com"
$incomingPassword  = "**********"
$incomingServer    = "pop.gmail.com"
$incomingPortPOP3  = 995   # Normally 110 (not secure), or 995 (SSL)
$incomingEnableSSL = $true

# Connect to the POP3 server and fetch messages
[Reflection.Assembly]::LoadFile( $openPopLibraryURL )

try {
   Write-Host "Connecting to POP3 server: $incomingServer`:$incomingPortPOP3"

   $pop3Client = makePOP3Client `
      $incomingServer $incomingPortPOP3 $incomingEnableSSL `
      $incomingUsername $incomingPassword

   Remove-Variable -Name incomingPassword

   fetchAndListMessages $pop3Client 10
   fetchAndSaveMessages $pop3Client 10 $tempBaseURL
   fetchAndSaveAttachments $pop3Client 10 $tempBaseURL
   loadAndListMessage $testMessageURL

   Write-Host "Disconnecting from POP3 server: $incomingServer`:$incomingPortPOP3"

   if ( $pop3Client.connected )
      {
      $pop3Client.disconnect()
      }

   $pop3Client.dispose()

   Remove-Variable -Name pop3Client
   }

catch { Write-Error "Caught exception:`n`t$PSItem" }

2.8 Notes and Caveats

First and foremost, be aware that incoming email messages is a huge threat vector for your IT infrastructure. Therefore, most of the problems you will face will be security related. This article does not address security and design issues, such as:

3 Conclusion

In this article, we have demonstrated that one can leverage on the OpenPOP.NET library to control a POP3 inbox with minimal boilerplate code and with only a handful of functions. The POP3 protocol is suitable for performing simple PowerShell tasks, such as fetch-forward-delete operations that does not involve complex inbox management. However, implementing anything beyond a simple forwarding system will require a local database scheme for tracking incoming emails.

For future work, we could investigate using a more modern protocol for fetching emails, such as the Internet Message Access Protocol (IMAP) [17]. IMAP offers features, such as automatic message state tracking, partial fetching of messages, support for multiple connections to the same mailbox, server-side searches, and so forth. These features could potentially simplify email management on the client side, and thus making IMAP a good alternative to POP3 for PowerShell applications. Unfortunately, .NET Frameworks do not implement classes for IMAP communications either; and once again one must rely on third-party libraries to attain IMAP support. Potential libraries worth exploring include AE.Net.Mail [3], S22.Imap [4], MailSystem.NET [5], and imapx [6].

Ultimately, no matter what solution you go for, either POP3 or IMAP, keep your email management code as simple as possible. Always ask, is PowerShell a suitable environment for doing complex email communications? Perhaps there is a better way, such as deploying third party software, built specifically for the job. And as usual, have fun experimenting, but be safe when handling messages from untrusted sources.

References

  1. OpenPop.NET, SourceForge, Date Retrieved 14 March 2018
    https://sourceforge.net/projects/hpop/files/

  2. OpenPop.NET, NuGet, Date Retrieved 14 March 2018
    https://www.nuget.org/packages/OpenPop.NET/

  3. AE.Net.Mail - A C# POP/IMAP Client Library, GitHub, Date Retrieved 14 March 2018
    https://github.com/andyedinborough/aenetmail

  4. S22.Imap, GitHub, Date Retrieved 14 March 2018
    https://github.com/smiley22/S22.Imap

  5. MailSystem.NET, GitHub, Date Retrieved 14 March 2018
    https://github.com/pmengal/MailSystem.NET

  6. imapx, GitHub, Date Retrieved 14 March 2018
    https://github.com/azanov/imapx

  7. SmtpClient Class, Microsoft Developer Network, 2018, Date Retrieved 14 March 2018
    https://msdn.microsoft.com/en-us/library/system.net.mail.smtpclient(v=vs.110).aspx

  8. Defensive Programming, Wikipedia, The Free Encyclopedia, 8 March 2018, Date Retrieved 14 March 2018
    https://en.wikipedia.org/wiki/Defensive_programming

  9. libpng Denial-of-service Vulnerability, Vulnerability Note VU#684412, Software Engineering Institute, Carnegie Mellon University, 25 February 2014, Date Retrieved 14 March 2018
    https://www.kb.cert.org/vuls/id/684412

  10. Let less secure apps use your account, Google, Date Retrieved 21 March 2018
    https://support.google.com/accounts/answer/6010255?hl=en

  11. OpenPop.NET POP3 Library Documentation, OpenPOP.NET, Date Retrieved 21 March 2018
    http://hpop.sourceforge.net/documentation/index.html

  12. Examples Overview, OpenPOP.NET, Date Retrieved 21 March 2018
    http://hpop.sourceforge.net/examples.php

  13. Send-MailMessage, Microsoft Documentation, 2018, Date Retrieved 14 March 2018
    https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/send-mailmessage

  14. System.Net.Mail Namespace, Microsoft Developer Network, 2018, Date Retrieved 14 March 2018
    https://msdn.microsoft.com/en-us/library/system.net.mail(v=vs.110).aspx

  15. David H. Crocker, Standard for ARPA Internet Text Messages, RFC-822, Dept. of Electrical Engineering, University of Delaware, Newark, DE 19711, 13 August 1982, Date Retrieved 4 April 2018
    https://tools.ietf.org/html/rfc822/

  16. Message.ToMailMessage Method, OpenPop.NET POP3 Library Documentation, OpenPOP.NET, Date Retrieved 4 April 2018
    http://hpop.sourceforge.net/documentation/index.html

  17. M. Crispin, Internet Message Access Protocol - Version 4rev1, RFC-3501, University of Washington, March 2003, Date Retrieved 4 April 2018
    https://tools.ietf.org/html/rfc3501/

  18. M. Rose, Post Office Protocol - Version 3, RFC-1081, TWG, November 1988, Date Retrieved 4 April 2018
    https://tools.ietf.org/html/rfc1081/

  19. Jonathan B. Postel, Simple Mail Transfer Protocol, RFC-821, Information Sciences Institute, University of Southern California, August 1982, Date Retrieved 4 April 2018
    https://tools.ietf.org/html/rfc821/

Appendix

Source Code and Binaries