decode.smime.lib.ps1

<#PSScriptInfo
 
    .VERSION 1.0.1
 
    .GUID 7ec44055-f24b-455d-93df-e7b7e1797391
 
    .AUTHOR rob.kalmar
 
    .COMPANYNAME
 
    .COPYRIGHT Rob Kalmar
 
    .TAGS SMIME Email
 
    .LICENSEURI
 
    .PROJECTURI
 
    .ICONURI
 
    .EXTERNALMODULEDEPENDENCIES
 
    .REQUIREDSCRIPTS
 
    .EXTERNALSCRIPTDEPENDENCIES
 
    .RELEASENOTES
 
 
    .PRIVATEDATA
 
#>


<#
 
    .DESCRIPTION
    Decode encrypted/signed messages
    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 decode signed and encrypted s/mime messages
#
# Tested platforms:
# Windows Server 2008 R2 (Needs this hotfix: http://support.microsoft.com/kb/2480994)
# Windows Server 2012
# Windows Server 2012 R2
# Windows Server 2016
# Windows Server 2019
#
# Copyright 2016-2020 by Rob Kalmar

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 Get-AllHeaders
{
  param
  (
    [parameter(Mandatory=$true)][string]$Headers
  )

  [array]$AllHeaders = $Headers -split "`r`n"
  $Key = $null
  $Value = $null

  Foreach ($Line in $AllHeaders)
  {
    If ($Line.StartsWith(' ') -or $Line.StartsWith("`t"))
    {
      filter script:Headers
      {
        If ($_.$Key -ne $Value.Trim())
        {
          $_
        }
      }
      $AllHeaders = $AllHeaders | Headers
      
      $Value = $Value + $Line 
    }
    Else
    {
      $Key,$Value = $Line -split ':', 2
    }
    
    If ($Value -ne $null)
    { 
      $AllHeaders += @{$Key = $Value.Trim()}
    }   
  }

  Return $AllHeaders
}

function New-MailMessage
{
  param
  (
    [parameter(Mandatory=$true)][string]$Message
  )

  [array]$AllMessageItems = $null

  $Headers = $null
  $Body = $null
  
  $Headers,$Body = $Message -split "`r`n`r`n", 2
  
  $AllMessageItems = Get-AllHeaders -Headers $Headers
  
  filter script:MessageItems
  {
    If (($_.Keys -eq 'To') -or ($_.Keys -eq 'Subject') -or ($_.Keys -eq 'Content-Type') -or ($_.Keys -eq 'Content-Transfer-Encoding') -or ($_.Keys -eq 'Content-Disposition') -or ($_.Keys -eq 'From'))
    {
      $_
    }
  }
  $AllMessageItems = $AllMessageItems | MessageItems
  $AllMessageItems += @{'Body' = $Body}
  
  $Boundary = $null
  $ContentType = ($AllMessageItems.'Content-Type' -split ';').Trim()  
  $Boundary = $ContentType -match 'boundary=*'
  If ($Boundary)
  {
    $Boundary = (($Boundary -split '=', 2)[1]).Trim('"')
    $Boundary = '--' + $Boundary
  }
  $AllMessageItems += @{'Boundary' = $Boundary}
 
  $MessageObject = new-object -TypeName PSObject
  $MessageObject | Add-Member -Name To -Value $AllMessageItems.'To' -MemberType NoteProperty
  $MessageObject | Add-Member -Name From -Value $AllMessageItems.'From' -MemberType NoteProperty
  $MessageObject | Add-Member -Name Subject -Value $AllMessageItems.'Subject' -MemberType NoteProperty
  $MessageObject | Add-Member -Name ContentType -Value $AllMessageItems.'Content-Type' -MemberType NoteProperty
  $MessageObject | Add-Member -Name ContentTransferEncoding -Value $AllMessageItems.'Content-Transfer-Encoding' -MemberType NoteProperty  
  $MessageObject | Add-Member -Name ContentDisposition -Value $AllMessageItems.'Content-Disposition' -MemberType NoteProperty
  $MessageObject | Add-Member -Name Boundary -Value $AllMessageItems.'Boundary' -MemberType NoteProperty
  $MessageObject | Add-Member -Name Body -Value $AllMessageItems.'Body' -MemberType NoteProperty
   
  Return $MessageObject
}

Function Get-ContentOid
{
  param
  (
    [parameter(Mandatory=$true)][byte[]]$Content
  )

  Add-Type -AssemblyName System.Security
  
  Try
  {
    $Oid = [Security.Cryptography.Pkcs.ContentInfo]::GetContentType($Content)
    $Result = $Oid.Value
  }
  Catch
  {
    $Result = 'Error getting Oid of content.'
  }
   
  Return $Result
}


function New-DecryptedContent
{ 
  param
  (
    [parameter(Mandatory=$true)][byte[]]$Content,
    [Security.Cryptography.X509Certificates.X509Certificate2]$Certificate = $null
  )

  Add-Type -AssemblyName System.Security
  
  $DecryptedBytes = $null

  $ContentInfo = New-Object -TypeName System.Security.Cryptography.Pkcs.ContentInfo -ArgumentList (,$Content) 
  $EnvelopedCMS = New-Object -TypeName System.Security.Cryptography.Pkcs.EnvelopedCms -ArgumentList $ContentInfo 

  Try
  {
    $null = $EnvelopedCMS.Decode($Content) 
  }
  Catch
  {
    Write-Verbose -Message 'Could not decode data.'
    Return $False
  }
  
   
  If ($Certificate -eq $null)
  {
    Try
    {
      $null = $EnvelopedCMS.Decrypt()
      [byte[]]$DecryptedBytes = $EnvelopedCMS.ContentInfo.Content
    }
    Catch
    {
      Write-Verbose -Message 'Could not decrypt data.'
      Return $False
    }
  }
  Else
  {
    Try
    {
      $CertificateCollection = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2Collection -ArgumentList $Certificate
      $null = $EnvelopedCMS.Decrypt($CertificateCollection)
      [byte[]]$DecryptedBytes = $EnvelopedCMS.ContentInfo.Content
    }
    Catch
    {
      Write-Verbose -Message 'Could not decrypt data.'
      Return $False
    }
  }

  $ContentInfo = $null  
  $EnvelopedCMS = $null
    
  Return $DecryptedBytes
}

function Assert-EnhancedProtection
{
  param
  (
    [parameter(Mandatory=$true)][Security.Cryptography.Pkcs.SignedCms]$SignedCMS,
    [string]$From = '',
    [string]$To = '',
    [string]$Subject = ''
  )
  
  $SignerInfos = $SignedCMS.SignerInfos
  $SignedAttributes = $SignerInfos.SignedAttributes
  
  Foreach ($SignedAttribute in $SignedAttributes)
  {
    $Oid = $SignedAttribute.Oid.Value
    Switch ($Oid)
    {
      '1.3.6.1.4.1.311.88.2.1' #DocumentName
      {
        $DocumentName = $SignedAttribute.Values.DocumentName
        If (-Not ( ($To -eq $DocumentName) -or ($To -eq "<$DocumentName>") ) )
        {
          Write-Verbose -Message "Enhanced protection: Message 'To' field does NOT match signed attribute 'To' Field! [To: $To SignedAttribute: $DocumentName]"
          Return $False
        }
        Else
        {
          Write-Verbose -Message "Enhanced protection: Message 'To' field matches signed attribute 'To' Field! [To: $To SignedAttribute: $DocumentName]"
        }
      }

      '1.3.6.1.4.1.311.88.2.2' #DocumentDescription
      {
        $DocumentDescription = $SignedAttribute.Values.DocumentDescription
        If ($Subject -ne $DocumentDescription)
        {
          Write-Verbose -Message "Enhanced protection: Message 'Subject' field does NOT match signed attribute 'Subject' Field! [Subject: $Subject SignedAttribute: $DocumentDescription]"
          Return $False
        }
        Else
        {
          Write-Verbose -Message "Enhanced protection: Message 'Subject' field matches signed attribute 'Subject' Field! [Subject: $Subject SignedAttribute: $DocumentDescription]"
        }
      }
    }
  }
  
  $SenderCertificate = $SignerInfos.Certificate
  $SenderCetificateName = $($SenderCertificate.GetNameInfo('EmailName', $False))
  If ($SenderCetificateName -ne $From)
  {
    Write-Verbose -Message "Enhanced protection: Message 'From' field does NOT match Certificate subject name used to sign this message! [From: $From Certificate subject name: $SenderCetificateName]"
    Return $False
  }
  Else
  {
    Write-Verbose -Message "Enhanced protection: Message 'From' field matches Certificate subject name used to sign this message! [From: $From Certificate subject name: $SenderCetificateName]"
  }
  
  Return $True
}

function New-VerifiedSignatureContent
{
  param
  (
    [parameter(Mandatory=$true)][byte[]]$Content,
    [byte[]]$Signature = $null,
    [bool]$Detached = $False,
    [string]$From = '',
    [string]$To = '',
    [string]$Subject = '',
    [bool]$VerifySignatureOnly = $False,
    [bool]$EnhancedProtection = $True
  )
  
  Add-Type -AssemblyName System.Security
  
  $UnsignedBytes = $null
  
  $ContentInfo = New-Object -TypeName System.Security.Cryptography.Pkcs.ContentInfo -ArgumentList (,$Content) 
  $SignedCMS = New-Object -TypeName System.Security.Cryptography.Pkcs.SignedCms -ArgumentList $ContentInfo, $Detached

  If ($Signature -eq $null)
  {
    $Signature = $Content
  }

  Try
  {
    $null = $SignedCMS.Decode($Signature)
  }
  Catch
  {
    Write-Verbose -Message 'Could not decode message.'
    Return $False
  }
  
  If ($EnhancedProtection)
  {
    $EnhancedProtected = Assert-EnhancedProtection -SignedCMS $SignedCMS -From $From -To $To -Subject $Subject
    If (-Not($EnhancedProtected))
    {
      Write-Verbose -Message 'Failed Enhanced Protection check.'
      Return $False
    } 
  }
  
  Try
  {
    $null = $SignedCMS.CheckSignature($VerifySignatureOnly)
    [byte[]]$UnsignedBytes = $SignedCMS.ContentInfo.Content
  }
  Catch
  {
    Write-Verbose -Message "Signature check failed. VerifySignatureOnly: $VerifySignatureOnly"
    Return $False
  }

  $ContentInfo = $null  
  $SignedCMS = $null
   
  Return $UnsignedBytes
}

function New-EscBoundary
{
  param
  (
    [parameter(Mandatory=$true)][string]$Boundary
  )
  
  [string]$EscBoundary = $null
  Foreach ($Character in $Boundary.ToCharArray())
  {
    If ($Character -in '(', ')', '+', '.')
    {
      $EscBoundary += '\' + $Character
    }
    Else
    {
      $EscBoundary += $Character
    }
  }
  
  Return $EscBoundary
}

function Get-AllMimeParts
{
  param
  (
    [parameter(Mandatory=$true)][string]$Body,
    [parameter(Mandatory=$true)][string]$Boundary
  )

  $Boundary = New-EscBoundary -Boundary $Boundary

  $AllMimeParts = $Body -split $Boundary
  $RelevantMimeParts = New-Object -TypeName System.Collections.ArrayList
  
  Foreach ($MimePart in $AllMimeParts)  
  {
    If ($MimePart -ne '')
    {
      $TestMimePart = $null
      $TestMimePart = New-MailMessage -Message $MimePart
      
      If ($TestMimePart.ContentType -ne $null)
      {
        #Remove single \r\n from beginning of string and single! \r\n from end of string.
        $StrippedMimePart = $MimePart.Substring(2,$($MimePart.Length-4))
        $null = $RelevantMimeParts.Add($StrippedMimePart)
      }
    }
  }
  
  Return $RelevantMimeParts
}

function Get-MimePartFilename
{
  [CmdletBinding()]
  param
  (
    [array]$ContentType = $null,
    [array]$ContentDisposition = $null
  )

  $FileName = $null
  If ($ContentType -ne $null)
  {
    $FileName = ($ContentType[0].ToString()).SubString(5,($ContentType[0].Length-5)).Trim('"')
  }
  
  If (($FileName -eq $null) -and ($ContentDisposition -ne $null))
  {
    $FileName = ($ContentDisposition[0].ToString()).SubString(9,($ContentDisposition[0].Length-9)).Trim('"')
  }
  
  Return $FileName
}

function Get-MailMessageSubject
{
  [CmdletBinding()]
  param
  (
    [string]$Subject = $null
  )
  
  [string]$Output = $null
  
  If (($Subject -ne $null) -and ($Subject.StartsWith('=?utf-8?B?')))
  {
    # This should be encoded using UTF-8
    $Output = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Subject.SubString(10,$Subject.Length-12)))
  }
  Else
  {
    $Output = $Subject
  }
  
  Return $Output
}

function Convert-ToBytes
{
  param
  (
    [parameter(Mandatory=$true)][string]$InputString,
    [parameter(Mandatory=$true)][string]$Base64
  )
  
  [byte[]]$Output = $null
  
  If ($Base64 -eq 'base64')
  {    
    Try
    {
      $Output = [Convert]::FromBase64String($InputString)
    }
    Catch
    {
      Write-Verbose -Message 'Could not convert Base64 string to bytes.'
    }
  }
  Else
  {
    $Output = [Text.Encoding]::Default.GetBytes($InputString)
  }
  
  Return $Output
}

Function Invoke-ProcessMailMessage
{
  param
  (
    [parameter(Mandatory=$true)][PSObject]$MailMessage,
    [Security.Cryptography.X509Certificates.X509Certificate2]$DecryptionCertificate = $null,
    [bool]$VerifySignatureOnly = $False,
    [bool]$EnhancedProtection = $True,
    [parameter(Mandatory=$true)][string]$OutputLocation,
    [bool]$SaveOnlyAttachments = $True
  )

  $Output = $null
  $ContentType = $MailMessage.ContentType
  
  Switch -wildcard ($ContentType)
  {    
    'application/*pkcs7-mime;*'
    {
      $ContentTransferEncoding = $MailMessage.ContentTransferEncoding
      $Content = Convert-ToBytes -InputString $($MailMessage.Body) -Base64 $ContentTransferEncoding

      $ContentOid = Get-ContentOid -Content $Content
    
      Switch ($ContentOid)
      {
        #Signed message
        '1.2.840.113549.1.7.2'
        {
          Write-Verbose -Message 'signed message found'
          
          $From = $MailMessage.From
          $To = $MailMessage.To  
          $Subject = Get-MailMessageSubject -Subject $($MailMessage.Subject)
                  
          $Result = New-VerifiedSignatureContent -Content $Content -Signature $null -Detached $False -From $From -To $To -Subject $Subject -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection
          If (-Not($Result))
          {
            Return $False
          }
            
          $MailMessage = New-MailMessage -Message ([Text.Encoding]::Default.GetString($Result))
          $MailMessage.From = $From 
          $MailMessage.To = $To
          $MailMessage.Subject = $Subject 
        
          $Output = Invoke-ProcessMailMessage -MailMessage $MailMessage -DecryptionCertificate $DecryptionCertificate -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection -OutputLocation $OutputLocation -SaveOnlyAttachments $SaveOnlyAttachments
          
          Return $Output
        }
      
        #Encrypted message
        '1.2.840.113549.1.7.3'
        {
          Write-Verbose -Message 'encrypted message found'
          
          $From = $MailMessage.From
          $To = $MailMessage.To
          $Subject = Get-MailMessageSubject -Subject $($MailMessage.Subject)
          
          $Result = New-DecryptedContent -Content $Content -Certificate $DecryptionCertificate
          If (-Not($Result))
          {
            Return $False
          }
        
          $MailMessage = New-MailMessage -Message ([Text.Encoding]::Default.GetString($Result))
          $MailMessage.From = $From
          $MailMessage.To = $To
          $MailMessage.Subject = $Subject
          
          $Output = Invoke-ProcessMailMessage -MailMessage $MailMessage -DecryptionCertificate $DecryptionCertificate -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection -OutputLocation $OutputLocation -SaveOnlyAttachments $SaveOnlyAttachments
          
          Return $Output
        }
      
        #Cannot determine if signed and/or encrypted.
        'Error getting Oid of content.'
        {
          Write-Verbose -Message 'Error getting Oid of content.'
          Return $False
        }
      
      }
    
    }

    '*multipart/signed*'
    {
      Write-Verbose -Message 'multipart signed message found'
      
      $AllMimeParts = Get-AllMimeParts -Body $MailMessage.Body -Boundary $MailMessage.Boundary
    
      $Content = [Text.Encoding]::Default.GetBytes($AllMimeParts[0])
      
      $SignaturePart = New-MailMessage -Message $AllMimeParts[1]
      $Signature = Convert-ToBytes -InputString $($SignaturePart.Body) -Base64 $($SignaturePart.ContentTransferEncoding)
         
      $From = $MailMessage.From
      $To = $MailMessage.To
      $Subject = Get-MailMessageSubject -Subject $($MailMessage.Subject)  
     
      $Result = New-VerifiedSignatureContent -Content $Content  -Signature $Signature -Detached $True -From $From -To $To -Subject $Subject -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection
      If (-Not($Result))
      {
        Return $False
      }
      
      $MailMessage = New-MailMessage -Message ([Text.Encoding]::Default.GetString($Result))
      $MailMessage.From = $From
      $MailMessage.To = $To
      $MailMessage.Subject = $Subject
    
      $Output = Invoke-ProcessMailMessage -MailMessage $MailMessage -DecryptionCertificate $DecryptionCertificate -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection -OutputLocation $OutputLocation -SaveOnlyAttachments $SaveOnlyAttachments
      
      Return $Output
    }
  
    '*multipart/mixed*'
    {
      Write-Verbose -Message 'multipart mixed message found'
    
      $Output = $True
      $AllMimeParts = Get-AllMimeParts -Body $MailMessage.Body -Boundary $MailMessage.Boundary
      Foreach ($MimePart in $AllMimeParts)
      {
        $From = $MailMessage.From
        $To = $MailMessage.To  
        $Subject = $MailMessage.Subject
      
        $MailMessage = New-MailMessage -Message $MimePart
      
        $MailMessage.From = $From
        $MailMessage.To = $To
        $MailMessage.Subject = $Subject
    
        $Result = Invoke-ProcessMailMessage -MailMessage $MailMessage -DecryptionCertificate $DecryptionCertificate -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection -OutputLocation $OutputLocation -SaveOnlyAttachments $SaveOnlyAttachments   
        If (-Not($Result))
        {
          $Output = $False
        }
      }
      
      Return $Output
    }
  
    Default
    {
      Write-Verbose -Message 'single part found'   

      filter script:ContentType
      {
        If ($_ -match 'name')
        {
          $_
        }
      }
      [array]$ContentType = ($MailMessage.ContentType -split ';').Trim() | ContentType

      filter script:ContentDisposition
      {
        If ($_ -match 'filename')
        {
          $_
        }
      }
      [array]$ContentDisposition = ($MailMessage.ContentDisposition -split ';').Trim() | ContentDisposition
      
      $ContentTransferEncoding = $MailMessage.ContentTransferEncoding
      $Content = Convert-ToBytes -InputString $($MailMessage.Body) -Base64 $ContentTransferEncoding
           
      $FileName = Get-MimePartFilename -ContentType $ContentType -ContentDisposition $ContentDisposition
    
      $Exists = Test-Path -Path $OutputLocation
      If (-Not($Exists))
      {
        $null = New-Item -Path $OutputLocation -ItemType 'directory'
      }
    
      $DateTime = $([DateTimeOffset]::Now.ToString('o')) -replace '[/:.+]', '-'
      If ($FileName)        
      {
        $FileName = $FileName.Trim()
        If ($FileName.StartsWith('=?UTF-8?B?'))
        {
          $Filename = $FileName.replace('=?UTF-8?B?','')
          $Filename = $FileName.replace('?=','')
          $Filename = $FileName.replace(' ','')
          $FileName = [Convert]::FromBase64String($FileName)
          $FileName = [Text.Encoding]::UTF8.GetString($FileName)
        }

        Write-Verbose -Message "Saving file: $OutputLocation\$DateTime-$FileName"
        Try
        {
          $null = [IO.File]::WriteAllBytes("$OutputLocation\$DateTime-$FileName",$Content)
          $Output = $True
        }
        Catch
        {
          $Output = $False
        }
        
        Return $Output
      }
      Else
      {
        If (-Not($SaveOnlyAttachments))
        {
          Write-Verbose -Message "No Filename, saving as $OutputLocation\$DateTime-body.txt"
          
          Try
          {
            $null = [IO.File]::WriteAllBytes("$OutputLocation\$DateTime-body.txt",$Content)
            $Output = $True
          }
          Catch
          {
            $Output = $False
          }
          
          Return $Output
        }
        Else
        {
          Write-Verbose -Message 'No Filename, skipping...'
          Return $True
        }
      }
    }
  }
}


function Invoke-DecodeMailMessage
{
  param
  (
    [parameter(Mandatory=$true)][byte[]]$Message,
    [Security.Cryptography.X509Certificates.X509Certificate2]$DecryptionCertificate = $null,
    [bool]$VerifySignatureOnly = $False,
    [bool]$EnhancedProtection = $True,
    [string]$OutputLocation = '',
    [bool]$SaveOnlyAttachments = $True
  )
  
  # 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)
  
  # Default text encoding is now code page 28591
  $MessageContent = [Text.Encoding]::Default.GetString($Message)
  $MailMessage = New-MailMessage -Message $MessageContent
  
  #Remove trailing '.<cr><lf>' if present in message.
  $BodyLength = $MailMessage.Body.Length
  $TrailingBytes = $MailMessage.Body[$($BodyLength-3)..$BodyLength]
  If ($([Text.Encoding]::Default.GetString($TrailingBytes)) -eq ".`r`n")
  {
    $MailMessage.Body = $MailMessage.Body.Substring(0,$($BodyLength-4))
  }

  # Cleanup From and To field
  $From = $($MailMessage.From)
  If (($From -ne $null) -AND  ($From.Contains('<')))
  {
    $MailMessage.From = ($(($From -split '<')[1]) -split '>')[0]
  }
  $To = $($MailMessage.To)
  If (($To -ne $null ) -AND ($To.Contains('<')))
  {
    $MailMessage.To = ($(($To -split '<')[1]) -split '>')[0]
  }

  # Set save location if none is specified (current execution location plus localpart of From mail address
  If ($OutputLocation -eq '')
  {
    $OutputLocation = "$(Split-Path -Path $script:MyInvocation.MyCommand.Path)"
  }
  $From = ($($MailMessage.From) -split '\@')[0]
  $OutputLocation = "$OutputLocation\$From"
 
  # Process the MailMessage and start decoding it.
  $Result = Invoke-ProcessMailMessage -MailMessage $MailMessage -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection -DecryptionCertificate $DecryptionCertificate -OutputLocation $OutputLocation -SaveOnlyAttachments $SaveOnlyAttachments
  
  #Restore original code page encoding.
  $OutputEncoding = $OriginalOutputEncoding
  
  Return $Result
}

# example

# show license
$null = Show-License

# enter the directory which contains the encrypted and/or signed mail files:
# set to smtp sent folder in this example
$InputLocation = "$(Split-Path -Path $script:MyInvocation.MyCommand.Path)\Sent\*"
# enter the directory where to store the decoded messages:
$OutputLocation = "$(Split-Path -Path $script:MyInvocation.MyCommand.Path)\Decoded"

# set security settings:
# if set to $True only the digital signatures or verified
# if set to $False also the signers certificate is validated and the purpose of the certificate
$VerifySignatureOnly = $True
# this checks signed attributes (if present) to the signed part
# it checks the recipient address to Pkcs9DocumentName
# and checks the subject to Pkcs9DocumentDescription
# also checks if the From: field matches the mail address used in the signing certificate.
$EnhancedProtection = $True

# set the decyption certificate:
# if set to $null the Windows Certificate store will be used
# normally you would use:
# $CertFile = "$(Split-Path -Path $script:MyInvocation.MyCommand.Path)\certificates\persontwo.pfx"
# $CertPassword = 'password'
#
# for this example we use:
$CertBase64 = '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
  cHY2eHMubmxfUm9vdC1TZWN1cmUtQ0EoMSkuY3J0MCkGCCsGAQUFBzABhh1odHRwOi8vYmx1ZWJv
  eC5pcHY2eHMubmwvb2NzcDBFBgNVHREEPjA8oCQGCisGAQQBgjcUAgOgFgwUcGVyc29uLnR3b0Bp
  cHY2eHMubmyBFHBlcnNvbi50d29AaXB2NnhzLm5sMA0GCSqGSIb3DQEBCwUAA4ICAQA0hbYLbI44
  ERZ4x2wY5hVqvgzsZBnXpMZtkL/JQ62JUwxIdQBxIYH+LwTNeWpgDoXxysMzT/Lg2CXus/OsNeHc
  Z6KddBIIXlfPICkfHTlwTlTYzSC+T5bHFB8+60fXe0QSoIefhIvOBTyEMjE0KH9Ud0UTlmMY6aCG
  vpuJGKj+ZOPq1GcUCeGuQByBzTV+e6sppyfpxTR+hNSxKPLo5uZ7dzz7iNbNqDuelNjZm2KVmQG7
  HVvGw4pTqcXfg6e9pTPW28GCE9yKA8OUqqh6TwHv7RwquvM/DtEuqqT31CfMphUgmV2oT3fjLES9
  q2UL9wT+gBrsdWvyk6eqY8/aQOFYkd+m22xJ/9mbDslqilzJTpWMpfLW24scBc2PUHxdugorZKaq
  g30tMmzLkG1er6zFfZ0hIZf+7VW3FnwNsu5vhTi8PfcbUk/nKGnFeN8zVWo8/4C13EHfecsWTTaS
  3hXNd9K3qiwyMSkZ/1z6tntHm4gKP6y0ezc9JvI9viZ7pVnX49dlmMXWMpNkrXizFGS/USJSBt6k
  N4Lgksm+5zBQB5liTQ09clNQHK1FZABceoFuMvixkHmS+Dd2h0YdY/wkbZQL5gaPjLNeGh0NhSue
  JYlHtMcVESYehv4od3rg8LbMiI+949W0bN0FFsGDYJsy7vpj8YDpAEXVYvPQCtZs/DGCARYwEwYJ
  KoZIhvcNAQkVMQYEBAEAAAAwMgYKKwYBBAGCNxEDRzEkBCJvAHYAaABiAG8AeAAuAGkAcAB2ADYA
  eABzAC4AbgBsAAAAMIHKBgorBgEEAYI3EQNXMYG7BIG4AAAAAAAAAAACAAAAAAAAAAIAAABsAGQA
  YQBwADoAAAB7AEUANAA5ADcANgA0AEMAMwAtADYAMQA3ADEALQA0ADkAMwA0AC0AQQA5ADEAOAAt
  ADUANQAzADIAOQBGAEIANwAwADQAOAA5AH0AAABCAEwAVQBFAEIATwBYAC4AaQBwAHYANgB4AHMA
  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'

$CertBytes = [Convert]::FromBase64String($CertBase64)
$CertPassword = 'password'
$DecryptionCertificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList ($CertBytes, $CertPassword)

# should the bodytext of the mail also be saved:
$SaveOnlyAttachments = $False

# get all the encrypted/signed files to be processed:
$Files = Get-ChildItem -Path $InputLocation

# process each file
Foreach ($File in $Files)
{ 
  # read the message file
  $Message = [IO.File]::ReadAllBytes("$($File.FullName)")
  
  # decode the message
  $Result = Invoke-DecodeMailMessage -Message $Message -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection -DecryptionCertificate $DecryptionCertificate -OutputLocation $OutputLocation -SaveOnlyAttachments $SaveOnlyAttachments
  $Result
}