Save mail attachments with PowerShell

By | December 27, 2022

The following step-by-step guide should enable you to automate the process of downloading attachments from an O365 mailbox to a folder. Be aware that this gives access to any mail account that you enter in the code below, therefore don’t just consider functional aspects but also the security of your environment. You should also be aware that PowerAutomate offers out-of-the-box solutions to save mail attachment to the file system, a SharePoint document library or OneDrive.

With that said let’s get started:  

In Azure, open App registrations and create a registration. In this example, we call it MailAccess.

Decide whether you want to use the secret or a certificate. Microsoft recommendation is to use a certificate [link]. 

To create a certificate, you again have two options: You can buy a certificate from a certificate authority (CA) like Sectigo, Symantec, DigiCert, Thawte, GeoTrust, GlobalSign, GoDaddy, Entrust or you create your own cert. In the second case, you may use PowerShell or your internal CA. Let’s start with PowerShell: You may take the code from the Microsoft’s page Granting access via Azure AD App-Only [link]. Copy the script, adapt the parameters and run it. Example:

.\Create-SelfSignedCertificate.ps1 -CommonName MailAccess -StartDate 2022-12-27 -EndDate 2024-12-27 -Password (ConvertTo-SecureString -String “MailAccess” -AsPlainText -Force)

In Azure, go to your App Registration, select Certificates and Secrets, Certificates and Upload certificate to select the cert you just created.

Next, we’ll configure our App registration:

Click Authentication and select Add a platform

On the right side, select Mobile and desktop application

Under Configure Desktop + devices, select

Under custom redirect URLs, enter http://localhost

Under API permissions, click Add a permission

On the right side, select Microsoft Graph and Application permissions

Scroll down to Mail and select the options Read and ReadWrite (ReadWrite is only required for deleting mails). Click Add permissions.

Click Grant admin consent for … and confirm with Yes.  

Select Overview and make a copy of the Tenant ID and Application (client) ID

In the script code below, enter the IDs.

Open the .cer file that you exported at the beginning and copy the thumbprint of the certificate. Paste it to the script.

Before you run the script, load the MSAL.PS module

Import-module -Name MSAL.PS

Now run the script from PowerShell. If you want to run it frequently, consider using a scheduled task that launches the script with system permissions.

Your next consideration should be security. Limit access to the machine the script is running on. Consider whom to give access to the app registration and reviewing usage [link].

#------------------Begin Function------------------------------------------------------
function LogWrite { [CmdletBinding()]



[String]$WithLog = $true
) $LogTxt = "$LogTxt" switch($Severity) { I {Write-Host "$LogTxt" -ForegroundColor Green} ## Info S {Write-Host "$LogTxt" -ForegroundColor Yellow} ## Status W {Write-Warning "$LogTxt"} ## Warning E {Write-Host "$LogTxt" -ForegroundColor Red -BackgroundColor White} ## Error V {Write-Verbose "$LogTxt" -Verbose} ## Verbose D {Write-Host "$LogTxt" -ForegroundColor Yellow -BackgroundColor DarkCyan} ## Script DEBUG L {$WithLog = $True} ## In Log-Datei schreiben Default {Write-Host "$LogTxt" -ForegroundColor Green; $WithLog = $True} ## Standard } if ($WithLog -eq $True){ $LogTxt = "$(Get-Date -Format "yyyy.MM.dd HH:mm:ss") $LogTxt" Add-Content -LiteralPath $LogFilePath -Value $LogTxt -EV VOID } }
#--------------End Function---------------------------------------------------------------------------

$LogFilePath = $psscriptroot + "\DownloadMailAttachments.log"
LogWrite -LogFilePath $LogFilePath -LogTxt "Starting DownloadMailAttachment version 1.0" -Severity 'I'
LogWrite -LogFilePath $LogFilePath -LogTxt ("LogFile: " + $LogFilePath) -Severity 'I' -WithLog $false

$DownloadPath = $psscriptroot + "\Downloads"
if(!(Test-Path $DownloadPath))
New-Item -ItemType Directory -Path $DownloadPath
LogWrite -LogFilePath $LogFilePath -LogTxt ("Create Folder: Downloads go to " + $DownloadPath) -Severity I
Write-Error ($_ | Out-String)
LogWrite -LogFilePath $LogFilePath -LogTxt ($_ | Out-String) -Component -Severity E }
LogWrite -LogFilePath $LogFilePath -LogTxt ("Downloads go to " + $DownloadPath) -Severity I

$TenantId = '' #ENTER YOUR TENANT ID HERE "12345678-1234-1234-1234-123456789012"
$ClientId = '' #ENTER YOUR CLIENT ID HERE "12345678-1234-1234-1234-123456789012"



#--------------End Initialization----------------------------------------------------------------------

# SaveMailAttachementsTo
Import-Module -Name MSAL.PS -Force

# Use TLS 1.2 connection (Server OS don't use TLS1.2 by default) [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$ClientCertificate = Get-Item "Cert:\LocalMachine\My\$($thumbPrint)"
$token = Get-MsalToken -ClientId $clientID -TenantId $tenantID -ClientCertificate $ClientCertificate
LogWrite -LogFilePath $LogFilePath -LogTxt $_ -Severity E
LogWrite -LogFilePath $LogFilePath -LogTxt 'Are you running the script with admin rights?' -Severity E

# Inspect the Access Token using JWTDetails PowerShell Module
$accessToken = $token.AccessToken

$uri = "$mailUser/mailFolders/deleteditems"
$del = Invoke-RestMethod -Uri $uri -Headers @{Authorization=("bearer {0}" -f $accessToken)}
$deleteditemsfolderid = $

$url = "$mailUser/mailFolders/Inbox/messages"
$messagequery = $url + "?' $select-Id&'$filter=HasAttachments eq true"
$messages = Invoke-RestMethod $messagequery -Headers @{Authorization=("bearer {0}" -f $accessToken)}

foreach ($message in $messages.value)
$query = $url + "/" + $ + "/attachments"
$attachments = Invoke-RestMethod $query -Headers @{Authorization=("bearer {0}" -f $accessToken)}

foreach($attachment in $attachments.value)

$path = $DownloadPath + "\"+ $attachment.Name
LogWrite -LogFilePath $LogFilePath -LogTxt ("Downloading " + $attachment.Name) -Severity I

$content = [System.Convert]::FromBase64String($attachment.ContentBytes)
Set-Content -Path $path -Value $content -Encoding Byte

$query = $url + "/" + $ + "/move"

$body = "{""DestinationId"": ""$deleteditemsfolderid""}"

Invoke-RestMethod $query -Body $body -ContentType "Application/json" -Method Post -Headers @{Authorization=("bearer {0}" -f $accessToken)}

LogWrite -LogFilePath $LogFilePath -LogTxt "Ending DownloadMailAttachment version 1.0" -Severity I


Quickstart: Register an application with the Microsoft identity platform

Granting access via Azure AD App-Only

Powershell script to audit all Azure AD app registrations and notify secret key or certificate expiration 

Tutorial: Govern and monitor applications

Leave a Reply

Your email address will not be published. Required fields are marked *