pop3.lib.ps1

<#PSScriptInfo
 
.VERSION 1.0.2
 
.GUID e6ed7ed6-31c4-45e8-9a64-8932caaec83e
 
.AUTHOR rob.kalmar
 
.COMPANYNAME
 
.COPYRIGHT Rob Kalmar
 
.TAGS POP3 Email SSL
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
 
.PRIVATEDATA
 
#>


<#
 
.DESCRIPTION
 Retrieve messages using POP3 protocol
 If you like this script please consider donating:
 https://bunq.me/robkalmar/5.00/PowerShellSMIMEToolkit
 Thanks!
 
#>
 

# PowerShell S/MIME Toolkit v1.0
#
#
# Copyright 2020 Rob Kalmar
# 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.
#
# Functions to receive mails with POP3 protocol
#
# Tested platforms:
# Windows Server 2008 R2
# Windows Server 2012
# Windows Server 2012 R2
# Windows Server 2016
# Windows Server 2019

Function Show-License
{
  [CmdletBinding()]
  param ()

  Add-Type -AssemblyName System.Drawing
  Add-Type -AssemblyName System.Windows.Forms

  $Form1 = New-Object -TypeName System.Windows.Forms.Form
  $Form1.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (400,450)
  $Form1.Text = 'PowerShell S/MIME Toolkit v1.0 license'
  $Form1.StartPosition = 'CenterScreen'
  $Form1.FormBorderStyle = 'FixedDialog'
  $Form1.MaximizeBox = $false
  $Form1.MinimizeBox = $false

  $License = 'Copyright 2020 Rob Kalmar
 
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.'


  $Label1 = New-Object -TypeName System.Windows.Forms.Label
  $Label1.Location = New-Object -TypeName System.Drawing.Size -ArgumentList (10,20) 
  $Label1.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (380,280) 
  $Label1.Text = $License
  $null = $Form1.Controls.Add($Label1) 

  $Label2 = New-Object -TypeName System.Windows.Forms.Label
  $Label2.Location = New-Object -TypeName System.Drawing.Size -ArgumentList (10,320) 
  $Label2.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (380,20) 
  $Label2.Text = 'If you like these PowerShell scripts please feel free to make a donation...'
  $null = $Form1.Controls.Add($Label2) 

  $LinkLabel1 = New-Object -TypeName System.Windows.Forms.LinkLabel 
  $LinkLabel1.Location = New-Object -TypeName System.Drawing.Size -ArgumentList (10,340) 
  $LinkLabel1.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (150,20) 
  $LinkLabel1.LinkColor = 'BLUE' 
  $LinkLabel1.ActiveLinkColor = 'RED' 
  $LinkLabel1.Text = 'Make donation...' 
  $null = $Form1.Controls.Add($LinkLabel1) 
  
  $null = $LinkLabel1.Add_Click(
    {
      [Diagnostics.Process]::Start('https://bunq.me/robkalmar/5.00/PowerShellSMIMEToolkit')
    }
  ) 

  $Label3 = New-Object -TypeName System.Windows.Forms.Label
  $Label3.Location = New-Object -TypeName System.Drawing.Size -ArgumentList (10,360) 
  $Label3.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (380,20) 
  $Label3.Text = 'Thank you!'
  $null = $Form1.Controls.Add($Label3) 

  $Button1 = New-Object -TypeName System.Windows.Forms.Button
  $Button1.Location = New-Object -TypeName System.Drawing.Size -ArgumentList (290,380) 
  $Button1.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (80,20) 
  $Button1.Text = 'Close'
  $null = $Form1.Controls.Add($Button1)    

  $null = $Button1.Add_Click(
    {    
      $Form1.Close()
    }
  )

  $null = $Form1.ShowDialog()
  
  Return $null
}

function Show-Error
{
  param
  (
    [parameter(Mandatory=$true)][Management.Automation.ErrorRecord]$ThrownError
  )

  $Error.Clear()
  Return $False
}

Function Connect-POP3Server
{    
  [CmdletBinding()]
  param ()

  $Connection = New-Object -TypeName System.Net.Sockets.TcpClient -ArgumentList $This.FQDN, $This.Port
    
  If ($This.SSL -eq $False)
  {
    $Stream = $Connection.GetStream()
  }
  Else
  {
    $sslStream = $Connection.GetStream()

    If ($This.VerifyCertificate -eq $False)
    {
      $Stream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList $sslStream, $False, ([Net.ServicePointManager]::ServerCertificateValidationCallback = { Return $True })
    }
    Else
    {
      $Stream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList $sslStream, $False, ([Net.ServicePointManager]::ServerCertificateValidationCallback = $null)
    }
    
    Try
    {
      $null = $Stream.AuthenticateAsClient("$($This.FQDN)")
    }
    Catch
    {
      Return $False
    }
    
  }

  $This.Connected = $True
  $This.Stream = $Stream
  
  [int]$BufferSize = 20480
  $Buffer = New-Object -TypeName byte[] -ArgumentList $BufferSize
  $NumberOfBytes = $This.Stream.Read($Buffer, 0, $Buffer.Length)
  $Response = [Text.Encoding]::Default.GetString($Buffer, 0, $NumberOfBytes)
  $This.Response = $Response.Trim("`r`n").Split("`n", 2)[0]
 
  Return $True
}


Function Invoke-Authentication
{
  [CmdletBinding()]
  param ()
  
  $Result = Submit-POP3Command -Command 'USER' -Parameter1 "$($This.Username)"

  If (($Result -eq $True) -and ($($This.Response.SubString(0,3)) -eq '+OK'))
  {
    $null = Submit-POP3Command -Command 'PASS' -Parameter1 "$($This.Password)"
    
    If ($($This.Response.SubString(0,3)) -eq '+OK')
    {
      $This.Authenticated = $True
    }
   
  }
  
  Return $This.Authenticated
}


Function Save-Message
{
  [CmdletBinding()]
  param
  (
    [int]$MessageNr = 1
  )
   
  $Result = Submit-POP3Command -Command 'RETR' -Parameter1 $MessageNr
  
  If (($Result -eq $True) -and ($($This.Response.SubString(0,3)) -eq '+OK'))
  {
    $MessageBytes = [Text.Encoding]::Default.GetBytes("$($This.ResponseMultiline)")
    
    $Exists = Test-Path -Path $($This.Output)
    If (-Not($Exists))
    {
      $Result = New-Item -Path $($This.Output) -ItemType 'directory'
      If ($Result -eq $null)
      {
        Return $False
      }
    }
    
    $Result = Submit-POP3Command -Command 'UIDL' -Parameter1 $MessageNr
    
    If ($Result -eq $True)
    {
      $Filename = $($This.Response.Split(' ')[2])
      $null = [IO.File]::WriteAllBytes("$($This.Output)\$Filename.eml",$MessageBytes)
    }
    Else
    {
      Return $False
    }
    
    Return $True
  }
  Else
  {
    Return $False
  }
  
}

Function Disconnect-POP3Server
{
  [CmdletBinding()]
  param ()
  
  $Result = Submit-POP3Command -Command 'QUIT'
  
  If ($Result)
  {
    $This.Authenticated = $False
    $This.Connected = $False
  }
  
  Return $Result
}

Function Get-NumberOfMessages
{
  [CmdletBinding()]
  param ()
  
  $Result = Submit-POP3Command -Command 'STAT'
  
  If ($Result -eq $True)
  {
    $NumberOfMessages = $This.Response.SubString(4)
    $pos = $NumberOfMessages.IndexOf(' ')
    [int]$NumberOfMessages = [convert]::ToInt32($NumberOfMessages.SubString(0,$pos), 10)
  }
  Else
  {
    Return $False
  }

  Return $NumberOfMessages
}

Function Submit-POP3Command
{
  param
  (
    [parameter(Mandatory=$true)][ValidateSet('USER','PASS','QUIT','RETR','DELE','UIDL','LIST','STAT','TOP','NOOP','RSET')][string]$Command,
    [string]$Parameter1 = '',
    [string]$Parameter2 = ''
  )
  
  If ($This.Connected -ne $True)
  {
    If ($Command -eq 'PASS')
    {
      $Parameter1 = 'xxxxxxxxx'
    }
    
    Return $False
  }
  
  # Store current code page default encoding and switch to code page default 28591 encoding to get full byte/character mapping
  $OriginalOutputEncoding = $OutputEncoding
  $OutputEncoding = [Text.Encoding]::GetEncoding(28591)
   
  $ResponseMultiline = New-Object -TypeName System.Text.StringBuilder
  $Response = $null
  
  [int]$BufferSize = 20480
  $Buffer = New-Object -TypeName byte[] -ArgumentList $BufferSize
  
  $CommandText = "$Command $Parameter1 $Parameter2".Trim()
  $CommandBytes = [Text.Encoding]::Default.GetBytes("$CommandText`r`n")
  $null = $This.Stream.Write($CommandBytes,0,$CommandBytes.Length)
  $null = $This.Stream.Flush()
  
  $NumberOfBytes = $This.Stream.Read($Buffer, 0, $Buffer.Length)
  $Response = (([Text.Encoding]::Default.GetString($Buffer, 0, $NumberOfBytes)).Split("`n", 2)[0]).Trim("`r`n")
  $null = $ResponseMultiline.Append(([Text.Encoding]::Default.GetString($Buffer, 0, $NumberOfBytes)).Split("`n", 2)[1])
  
  If ($($Response.SubString(0,3)) -ne '+OK')
  {
    $This.Response = $Response
    $This.ResponseMultiline = $ResponseMultiline
    Return $False
  }
  
  If ($Parameter1 -eq '')
  {
    $MultilineOutputCommand = 'UIDL','LIST'
  }
  Else
  {
    $MultilineOutputCommand = 'RETR','TOP'
  }
  
  If ($MultilineOutputCommand.Contains($Command))
  {
    $End = "`r`n.`r`n"
    $EndPart = ''
    If ($ResponseMultiline.Length -ge 5)
    {
      $EndPart = $ResponseMultiline.ToString($ResponseMultiline.Length-5,5)
    }
    
    While ($End -ne $EndPart)
    {
       $NumberOfBytes = $This.Stream.Read($Buffer, 0, $Buffer.Length)
       $null = $ResponseMultiline.Append([Text.Encoding]::Default.GetString($Buffer, 0, $NumberOfBytes))
       $EndPart = $ResponseMultiline.ToString($ResponseMultiline.Length-5,5)
    }
  }
  
  $This.Response = $Response
  $This.ResponseMultiline = $ResponseMultiline
 
  $OutputEncoding = $OriginalOutputEncoding
  
  Return $True
}


Function New-POP3Object
{
  param
  (
    [parameter(Mandatory=$true)][string]$FQDN,
    [string]$Output = $(Split-Path -Path $script:MyInvocation.MyCommand.Path) + '\received',
    [string]$Port = '110',
    [bool]$SSL = $False,
    [bool]$VerifyCertificate = $True,
    [string]$Username = '',
    [string]$Password = '',
    [int]$Timeout = 10000
  )

  # initialize properties
  $POP3Object = new-object -TypeName PSObject
  $POP3Object | Add-Member -Name FQDN -Value $FQDN -MemberType NoteProperty
  $POP3Object | Add-Member -Name Output -Value $Output -MemberType NoteProperty
  $POP3Object | Add-Member -Name Port -Value $Port -MemberType NoteProperty
  $POP3Object | Add-Member -Name SSL -Value $SSL -MemberType NoteProperty
  $POP3Object | Add-Member -Name VerifyCertificate -Value $VerifyCertificate -MemberType NoteProperty
  $POP3Object | Add-Member -Name Username -Value $Username -MemberType NoteProperty
  $POP3Object | Add-Member -Name Password -Value $Password -MemberType NoteProperty
  $POP3Object | Add-Member -Name Timeout -Value $Timeout -MemberType NoteProperty
  $POP3Object | Add-Member -Name Connected -Value $False -MemberType NoteProperty
  $POP3Object | Add-Member -Name Authenticated -Value $False -MemberType NoteProperty
  $POP3Object | Add-Member -Name Stream -Value $null -MemberType NoteProperty
  $POP3Object | Add-Member -Name Response -Value $null -MemberType NoteProperty
  $POP3Object | Add-Member -Name ResponseMultiline -Value $null -MemberType NoteProperty
   
  # methods
  $POP3Object | Add-Member -MemberType ScriptMethod -Name Connect -Force -Value { 
    [CmdletBinding()]
    param ()
    Try
    {
      $Connected = Connect-POP3Server
    }
    Catch
    {
      $Connected = Show-Error -ThrownError $_
    }
    
    Return $Connected
  }
  
  $POP3Object | Add-Member -MemberType ScriptMethod -Name Authenticate -Force -Value { 
    [CmdletBinding()]
    param ()
    Try
    {
      $Authenticated = Invoke-Authentication
    }
    Catch
    {
      $Authenticated = Show-Error -ThrownError $_
    }
    
    Return $Authenticated
  }
  
  $POP3Object | Add-Member -MemberType ScriptMethod -Name SubmitCommand -Force -Value { 
    [CmdletBinding()]
    param 
    (
      [string]$Command = '',
      [string]$Parameter1 = '',
      [string]$Parameter2 = ''
    )
    Try
    {
      $Result = Submit-POP3Command -Command $Command -Parameter1 $Parameter1 -Parameter2 $Parameter2
    }
    Catch
    {
      $Result = Show-Error -ThrownError $_
    }
    
    Return $Result
  }
  
  $POP3Object | Add-Member -MemberType ScriptMethod -Name Close -Force -Value { 
    [CmdletBinding()]
    param ()
    Try
    {
      $Disconnected = Disconnect-POP3Server
    }
    Catch
    {
      $Disconnected = Show-Error -ThrownError $_
    }
    
    Return $Disconnected
  }
  
  $POP3Object | Add-Member -MemberType ScriptMethod -Name SaveMessage -Force -Value { 
    [CmdletBinding()]
    param 
    (
      [int]$MessageNr = 1
    )
    Try
    {
      $Saved = Save-Message -MessageNr $MessageNr
    }
    Catch
    {
      $Saved = Show-Error -ThrownError $_
    }
    
    Return $Saved
  }
  
  $POP3Object | Add-Member -MemberType ScriptMethod -Name NumberOfMessages -Force -Value { 
    [CmdletBinding()]
    param ()
    Try
    {
      $NumberOfMessages = Get-NumberOfMessages
    }
    Catch
    {
      $NumberOfMessages = Show-Error -ThrownError $_
    }
    
    Return $NumberOfMessages
  }
  
  Return $POP3Object
}

# example

# show license
$null = Show-License

# enter your POP3 servername or IP address:
$FQDN = '<server>'
# enter the port to connect to (defaults: 110 for Non-SSL or 995 for SSL):
$Port = '995'
# use SSL to connect:
$SSL = $True
# should the server certificate be validated:
$VerifyCertificate = $True

# enter the username to authenticatie with:
$Username = '<username>'
# enter the password used to authenticate:
$Password = '<password>'

# location where to save downloaded messages
$Output = "$(Split-Path -Path $script:MyInvocation.MyCommand.Path)" + '\Received'

# create a new POP3 object using the details provided above:
$POP3 = New-POP3Object -FQDN $FQDN -Port $Port -SSL $SSL -VerifyCertificate $VerifyCertificate -Username $Username -Password $Password -Output $Output

# connect to the POP3 server
$null = $POP3.Connect()

# authenticate with the POP3 server
$null = $POP3.Authenticate()

# get the number of messages on the POP3 server for the logged on user
$NumberOfMessages = $POP3.NumberOfMessages()

# retreive messages
$Count = 1
While ($Count -le $NumberOfMessages)
{
  # retrieve message
  $null = $POP3.SaveMessage($Count)
  $Count = $Count + 1
}

# use SubmitCommand to send your command to the POP3 server
# allowed commands are: 'USER','PASS','QUIT','RETR','DELE','UIDL','LIST','STAT','TOP','NOOP','RSET'
# for certain commands you can also pass (2) parameter(s)
# see: https://tools.ietf.org/html/rfc1939 for more details.
# you can get the output by querying POP3.Response
# in case the command outputs multiple lines you can get the result by querying POP3.ResponseMultiline
$null = $POP3.SubmitCommand('LIST',$null,$null)
# get the output of the previous SubmitCommand:
$Result = $POP3.Response
$Result
$Result = $POP3.ResponseMultiline
# POP3.ResponseMultiline is a StringBuilder object so we need to convert to String
If ($Result -ne $null)
{
  $Result.ToString()
}

# close the connection with the POP3 server
$null = $POP3.Close()