PowerShell script to download attachments from an email

Recently I was asked to automate the process of grabbing an email from a mailbox and downloading all the attachments to a specified destination. The following script accomplishes this using the Exchange Managed Webservices API. A few notes about this script:

  1. It must be run by either the same account that owns the mailbox being searched or an account with the impersonation RBAC role assigned to it
  2. It requires the EWS API, and you can specify the path to your installed version
  3. The Script searches for any email with a similar supplied subject
  4. Optionally the script can do the following:
    1. Do an exact search for the subject supplied
    2. Delete the email after a successful download of all the attachments
    3. Can be set not to overwrite files with the same name in the target directory
    4. If the attachments are zip files it can extract the files to the specified location (does not work with overwrite protection)
    5. Search for only emails matching today’s date
    6. Send an email error report to a specified email (good if the script is going to be run through a simple scheduler like Scheduled Tasks

This script currently cannot download attached EML/MSG files or embedded attachments in those files

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
<#
.SYNOPSIS
Get-AttachmentFromEmail.ps1 will use Exchange Web Services to search the inbox of a given mailbox associated with an SMTP Address for any emails with attachments that are similar to the subject specified. If found all attachments in the emails found will be saved to the specified path. Options include the ability to search for just emails that arrived today, a search for an exact subject match, the ability to automatically unzip *.zip files, and the ability to delete the emails once the attachments have been successfully downloaded. The script also supports using impersonation to access a mailbox not belonging to the run time user
.DESCRIPTION
Before attempting any work the script checks for the existence of the EWS managed API, if it cannot be found the script will send an error to the console and to the email address specified to receive error reports
Once the EWS managed API is loaded a connection to Exchange is made to the mailbox of the current runtime user or using impersonation to connect to a mailbox of another user. Once connected the total items of in the mailbox being worked is found, this will be used to ensure the entire inbox is searched when looking for the email in question.
Then a search query is built to look for the following
1. Any email with a subject similar to the subject specified
a. The –ExactSearch parameter will search for emails with a subject that exactly matches the one specified
2. That also has attachments
This search will return all emails that match this query in the inbox, conversely the –TodayOnly switch can be used to return only emails matching the runtime date (e.g. 10/1/2013)
If any emails are found then the attachments are saved to the specified path (overwriting any files with the same name), if the path is not accessible then the script will send an error to the console and to the email address specified to receive error reports.
If the switch –UnZipFiles is used and the email attachments are indeed zip files then the script first copy the zip files to the destination path specified, un-compress the contents, and delete the zip file
If the switch –DeleteEmail is used then after all the attachments have been downloaded the emails found will be moved to the Deleted folder on the mailbox in question. 
.PARAMETER EmailSubject
The exact subject of the email that the script will search for
.PARAMETER SavePath
The local or network path the attachments found will be save to
.PARAMETER SMTPAddress
The SMTP address of the of the mailbox that the script will search for regardless if it's the current runtime user or a different user
.PARAMETER DeleteEmail
If specified any email found and it’s attachments successfully saved will be moved to the deleted folder. 
.PARAMETER TodayOnly
If specified only emails found that match the Day, Month, and Year of the current script runtime will be returned by the inbox search 
.PARAMETER ExactSearch
By default the script will search for any email matching any of the terms in the subject being searched for, when using this parameter the script will look for emails with a subject that exactly matches the subject being searched for
.PARAMETER UnZipFiles
If any attachments found are zip files you can specify that the script extract the files within the zip file and store them at the specified location instead of the zip file itself
.PARAMETER EwsUrl
Instead of using auto discover you can specify the EWS URL
.PARAMETER ExchangeVersion
Specify the Exchange Version the EWS service should use, the default is "Exchange2010_SP2"
.PARAMETER IgnoreSSLCertificate
Used to ignore any SSL certificate errors encountered when using EWS
.PARAMETER EWSManagedApiPath
Used to specify the path for the EWS Managed API, the default is C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll
.PARAMETER EWSTracing
Used to turn on EWS tracing for troubleshooting
.PARAMETER ImpersonationUserName
The Username of the account to user for impersonation
.PARAMETER ImpersonationPassword
The Password of the account to user for impersonation
.PARAMETER ImpersonationDomain
The Domain of the account to user for impersonation
.PARAMETER EmailFrom
The SMTP address error emails will come from
.PARAMETER EmailErrorsTo
The SMTP address error emails will go to
.PARAMETER SMTPServer
The SMTP address server used to send error emails 
.EXAMPLE
PS C:\> .\Get-AttachmentFromEmail.ps1 -EmailSubject “Important Attachment” –SavePath “\\FileServer\Email Attachments” -SMTPAddress John.Doe@company.com
 
Description
-----------
Will use the runtime account to access and search the mailbox associated with "John.Doe@company.com" for any emails in the inbox with a subject like “Important Attachment” and save the attachments to \\FileServer\Email Attachments
.EXAMPLE
PS C:\> .\Get-AttachmentFromEmail.ps1 -EmailSubject “Important Attachment” –SavePath “\\FileServer\Email Attachments” -SMTPAddress John.Doe@company.com -ExactSearch –UnZipFiles 
 
Description
-----------
Will use the runtime account to access and search the mailbox associated with "John.Doe@company.com" for any emails in the inbox with the exact subject of “Important Attachment” and save the attachments to \\FileServer\Email Attachments. If any of those attachments are zip files then the script will extract the contents of the zip files leaving only the contents and not the zip file
.EXAMPLE
PS C:\> .\Get-AttachmentFromEmail.ps1 -EmailSubject “Sales Results” –SavePath “C:\Email Attachments” -SMTPAddress John.Doe@company.com -DeleteEmail -TodayOnly 
 
Description
-----------
Will use the runtime account to access and search the mailbox associated with "John.Doe@company.com" for any emails in the inbox with a subject like “Sales Results” with the runtime date of today and save the attachments to \\FileServer\Email Attachments. Once the attachments have been saved then move the email to the delete items folder
.EXAMPLE
PS C:\> .\Get-AttachmentFromEmail.ps1 -EmailSubject “Important Attachment” –SavePath \\FileServer\Email Attachments -SMTPAddress differentUser@company.com –ImpersonationUserName ServiceMailboxAutomation –ImpersonationPassword “$*fnh23587” –ImpersonationDomain CORP
 
Description
-----------
Will search the mailbox associated with “differentUser@company.com” using the account “ServiceMailboxAutomation” which will need the impersonation RBAC role to access the mailbox. The search will be for any emails in the inbox with a subject like “Important Attachment” and save the attachments to \\FileServer\Email Attachments. 
.EXAMPLE
PS C:\> .\Get-AttachmentFromEmail.ps1 -EmailSubject “Important Attachment” –SavePath \\FileServer\Email Attachments -SMTPAddress John.Doe@company.com –EmailFrom EmailTeam@company.com –EmailErrorsTo HelpDesk@company.com –SMTPServer mail.company.com
 
Description
-----------
Will use the runtime account to access and search the mailbox associated with "John.Doe@company.com" for any emails in the inbox with a subject like “Important Attachment” and save the attachments to \\FileServer\Email Attachments. If any errors are encountered then send an email from “EmailTeam@company.com” to “HelpDesk@company.com” using the SMTP server of “mail.company.com”
.EXAMPLE
PS C:\> .\Get-AttachmentFromEmail.ps1 -EmailSubject “Important Attachment” –SavePath \\FileServer\Email Attachments -SMTPAddress John.Doe@company.com –EwsUrl https://webmail.company.com/EWS/Exchange.asmx –EWSManagedApiPath "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll" -$ExchangeVersion Exchange2010_SP1 –IgnoreSSLCertificate -EWSTracing
 
Description
-----------
Will use the runtime account to access and search the mailbox associated with "John.Doe@company.com" any emails in the inbox that exactly match “Important Attachment” and save the attachments to \\FileServer\Email Attachments. Before connecting to the mailbox the EWS connection will use the following options
1. The EWS URL of https://webmail.company.com/EWS/Exchange.asmx
2. The EWS Managed API Path of "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"
3. The Exchange version of "Exchange2010_SP1"
4. Ignore all SSL Certificate errors
5. Turn on EWS tracing
.INPUTS
System.String
You cannot pipe to this script
.OUTPUTS
NONE
.NOTES
AUTHOR: John Mello 
CREATED : 10/29/2013 
CREATED BECAUSE: To automate the retrieval, download, and deletion of a daily delivered email attachment 
.LINK
DSMS Script Storage location : \\Bala01\World\Technology\EnterpriseServices\PlatformServices\Directory and Messaging Services\Scripts
DSMS SharePoint Scripting Portal : http://techweb/ess/dci/dsms/scripting/default.aspx
DSMS PowerShell KB : http://techweb/ess/dci/dsms/scripting/powershellscripting/Home.aspx
#>
[CmdletBinding(DefaultParameterSetName="Default")]
Param(
[Parameter(Mandatory=$True,Position=0)]
[string]$EmailSubject,
 
[Parameter(Mandatory=$True,Position=1)]
[string]$SavePath,
 
[Parameter(Mandatory=$True,Position=3)]
[string]$SMTPAddress,
 
[Parameter(Mandatory=$false)]
[switch]$DeleteEmail,
 
[Parameter(Mandatory=$false)]
[switch]$TodayOnly,
 
[Parameter(Mandatory=$false)]
[switch]$ExactSearch,
 
[Parameter(Mandatory=$false)]
[switch]$UnZipFiles,
 
[Parameter(Mandatory=$false)]
[string]$EwsUrl = "https://webmail.susq.com/EWS/Exchange.asmx" ,
 
[Parameter(Mandatory=$false)]
[ValidateSet("Exchange2007","Exchange2007_SP1","Exchange2007_SP2","Exchange2007_SP3","Exchange2010","Exchange2010_SP1","Exchange2010_SP2","Exchange2010_SP3")]
[string]$ExchangeVersion = "Exchange2010_SP2",
 
[Parameter(Mandatory=$false)]
[switch]$IgnoreSSLCertificate,
 
[Parameter(Mandatory=$false)]
[string]$EWSManagedApiPath = "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll",
 
[Parameter(Mandatory=$false)]
[switch]$EWSTracing,
 
[Parameter(Mandatory=$True,ParameterSetName="Impersonation")]
[string]$ImpersonationUserName,
 
[Parameter(Mandatory=$True,ParameterSetName="Impersonation")]
[string]$ImpersonationPassword,
 
[Parameter(Mandatory=$false,ParameterSetName="Impersonation")]
[string]$ImpersonationDomain,
 
[Parameter(Mandatory=$False)] 
[string]$EmailFrom, 
 
[Parameter(Mandatory=$False)] 
[string[]]$EmailErrorsTo,
 
[Parameter(Mandatory=$False)] 
[string]$SMTPServer = "mailhost.susq.com"
)
 
#Region Functions
 
Function New-EWSServiceObject {
<#
.SYNOPSIS
Returns an EWS Service Object
.DESCRIPTION
Creates a EWS service object, with the option of using impersonation and/or An EWS URL or fall back to Autodiscover
.PARAMETER SMTPAddress
The SMTP address of the Exchange mailbox that is associated with the EWS object
.EXAMPLE
PS C:\powershell> New-EWSServiceObject -SMTPAddress "John.Doe@Company.com"
 
------------------
Description 
------------------
 
This will return and EWS object that uses impersonation with a specifed URL
#>
[CmdletBinding(DefaultParameterSetName="OtherUser")]
Param() 
 
Write-Verbose "Creating EWS Service connection object"
$EWSService = new-object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::$ExchangeVersion)
 
If ($EWSTracing) {
Write-Verbose "EWS Tracing enabled"
$EWSService.traceenabled = $true
}
 
if ($IgnoreSSLCertificate){
Write-Verbose "Ignoring any SSL certificate errors"
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true };
} 
 
If ($SMTPAddress) {Write-Verbose "Creating service for the following address $SMTPAddress"}
Else {Write-Verbose "Creating service for the following runtime user $ENV:UserName"} 
 
Write-Verbose "Checking if the runtime or a seperate account will be used for impersonation"
If ($ImpersonationUserName -and $ImpersonationPassword) {
Write-Verbose "Secondary account for Impersonation specifed ($ImpersonationUserName)"
If ($ImpersonationDomain) {
#If a domain is presented then use that as well
$EWSService.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($ImpersonationUserName,$ImpersonationPassword,$ImpersonationDomain)
}
Else {
#Otherwise leave the domain blank
$EWSService.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($ImpersonationUserName,$ImpersonationPassword)
}
Write-Verbose "Saving impersonation credentials for EWS service"
$EWSService.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $SMTPAddress)
}
Else{
Write-Verbose "Runtime account specifed for impersonation ($ENV:Username)"
$EWSService.UseDefaultCredentials = $true 
}
 
#Set the EWS URL, the EWS URL is needed if the the runtime user is specifed since they may not have a mailbox
If ($EWSURL) {
Write-Verbose "Using the specifed EWS URL of $EWSURL"
$EWSService.URL = New-Object Uri($EWSURL)
}
Else {
Write-Verbose "Using the AutoDiscover to find the EWS URL"
$EWSService.AutodiscoverUrl($SMTPAddress, {$True})
}
#Now Return the Service Object
Return $EWSService
}
 
Function Send-ErrorReport {
<#
.SYNOPSIS
Used to create a one line method to write an error warning to the console, send an error email, and exit a script when an terminal error is encountered
.DESCRIPTION
Send-ErrorReport Takes a Subject and message body field and uses the Subject to write a warning messages to the console and as the email subject. It uses the body as the body of the email that will be sent. You can also specify and SMTP Server, From, and To address or set the default options. Once complete the script sends the exit command to script calling the function 
.PARAMETER Subject
The subject of the email to be sent and the warning message that is sent to the console
.PARAMETER body 
The body of the email sent
.PARAMETER HaltScript
Sends the Exit command to the script caling the function, used to report on terminating errors
.EXAMPLE
PS C:\powershell> Send-ErrorReport -Subject "can't load EWS module" -Body "can't load EWS module, verify this path : $EWSManagedApiPath" -HaltScript
 
WARNING: can't load EWS module
 
------------------
Description 
------------------
 
This will send an email through the specifed SMTP server, from and to the specifed addresses with the specifed subject and body and use the sbubject to write a warning message. Then function will stop the script that called it
#>
[CmdletBinding()]
Param(
[Parameter(Position=0,Mandatory=$true)]
[string]$Subject,
 
[Parameter(Position=1,Mandatory=$true)]
[string]$body,
 
[Parameter()]
[switch]$HaltScript
) 
 
Write-Warning $Subject
#Appened Script name to email Subject
If ($EmailFrom -and $EmailErrorsTo -and $SMTPServer) {
$Subject = "Get-AttachmentFromEmail.ps1 : " + $Subject
send-mailmessage -from $EmailFrom -to $EmailErrorsTo -smtpserver $SMTPServer -subject $Subject -Body ($body | Out-String) -BodyAsHtml
}
If ($HaltScript) {Exit 1}
}
 
#EndRegion
 
#Region PreWork
 
#Remove double qoutes from Subject, File Path, and SMTP address (just in case)
#Need to find a way around this
$EmailSubject = $EmailSubject -replace "`""
$SavePath = $SavePath -replace "`""
$SMTPAddress = $SMTPAddress -replace "`""
 
# Check if the save path is available, if not then exit the script
Try {Test-Path $SavePath -ErrorAction Stop | Out-Null}
Catch {Send-ErrorReport -Subject "Save path cannot be accessed" -body "Please the following user name <B>$($ENV:USERNAME)<?B>, has access to this path : <B>$SavePath</B>" -HaltScript}
 
# Check if EWS Managed API available, if not then exit the script
Try {Get-Item -Path $EWSManagedApiPath -ErrorAction Stop | Out-Null}
Catch {Send-ErrorReport -Subject "EWS Managed API path cannot be accessed" -body "Please verify the following EWS API path on <B>$($ENV:COMPUTERNAME)</B> : <B>$EWSManagedApiPath</B>" -HaltScript}
 
# Load EWS Managed API
[void][Reflection.Assembly]::LoadFile($EWSManagedApiPath);
 
#EWS requires double back whacks "\\" for each normal back whack "\" when
#it comes to the file path to save to
#E.g. C:\Windows\System32 needs to look like C:\\Windows\\System32
$EWSSavePath = $SavePath -replace "\\","\\"
 
#Create EWS Service
$EWSservice = New-EWSServiceObject
#EndRegion
 
#Region Main Program Work
#Attach to the inbox to find the total number of items
#This will be used on in the search query so that we search the entire inbox
$InboxID = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
Try {$InboxFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($EWSservice,$InboxID)}
Catch {Send-ErrorReport -Subject "Account is not permissioned for Impersonation" -body "Please verify that the following account <B>$ImpersonationUserName</B> has the ability to impersonate <B>$SMTPAddress</B>" -HaltScript}
 
$NumEmailsToReturn = $InboxFolder.TotalCount
If ($InboxFolder.TotalCount -eq 0) {
Write-Warning "Inbox is empty, exiting script or the Runtime Account of $($ENV:USERNAME) does not have impersonation rights for $SMTPAddress"
Exit
}
 
#Create mailbox view
$view = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ItemView -ArgumentList $NumEmailsToReturn
#Define properties to pull for each item in the view
$propertyset = New-Object Microsoft.Exchange.WebServices.Data.PropertySet (
[Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly,
[Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject,
[Microsoft.Exchange.WebServices.Data.ItemSchema]::HasAttachments,
[Microsoft.Exchange.WebServices.Data.ItemSchema]::DisplayTo,
[Microsoft.Exchange.WebServices.Data.ItemSchema]::DisplayCc,
[Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeSent,
[Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived
)
#Assign view to porperty set
$view.PropertySet = $propertyset
#Define the search quuery
#An EWS exact search puts the search term in "" so if we need that then add the qoutes again)
If ($ExactSearch) {
$EmailSubject = "`"$EmailSubject`""
}
If ($TodayOnly) {$query = "Subject:$EmailSubject AND HasAttachments:True AND Received:today"}
Else {$query = "Subject:$EmailSubject AND HasAttachments:True"}
 
#Preform the search
$FoundEmails = $EWSservice.FindItems("Inbox",$query,$view)
#$FoundEmail = $items | Where-Object {$_.Subject -like $EmailSubject}
If ($FoundEmails) {
If ($UnZipFiles) {$ZipFileNames = @()}
Foreach ($Email in $FoundEmails) {
$emailProps = New-Object -TypeName Microsoft.Exchange.WebServices.Data.PropertySet(
[Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly,
[Microsoft.Exchange.WebServices.Data.ItemSchema]::Attachments
)
 
$EmailwithAttachments = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($EWSservice, $Email.Id, $emailProps)
Foreach ($File in $EmailwithAttachments.Attachments) {
Try {
#Just to be safe, remove any invaild characters from the attachment name
$FileName = $File.name -replace ("[{0}]" -f ([RegEx]::Escape([String][System.IO.Path]::GetInvalidFileNameChars())))
#Current can't handle Emails that are attacments, so we skip them
If ($File.contenttype -eq $null) {
Write-Warning "Attachment is an email, skipping"
$EMLAttachment = $TRUE
continue
}
Else {$EMLAttachment = $FALSE}
$File.load($EWSSavePath + "\\" + $FileName)
If (($FileName -like "*.zip") -and $UnZipFiles) {$ZipFileNames += $SavePath + "\"+ $FileName}
}
Catch {Send-ErrorReport -Subject "Attachment save path cannot be accessed" -body "Please verify the following path <B>$SavePath</B><BR>Full Error:<BR> $_" -HaltScript}
}
If ($DeleteEmail -and (-not $EMLAttachment)) {$Email.Delete("MoveToDeletedItems")}
Else {Write-Warning "DeleteEmail specifed but embedded EML file in email could not be downloaded"}
}
If ($UnZipFiles) {
Try {
$shell = new-object -com shell.application
$Location = $shell.namespace($SavePath)
foreach ($ZipFile in $ZipFileNames)
{
$ZipFolder = $shell.namespace($ZipFile)
$Location.Copyhere($ZipFolder.items())
Remove-item $ZipFile
}
}
Catch {Send-ErrorReport -Subject "Attachment save path cannot be accessed" -body "Please verify the following path <B>$SavePath</B><BR>Full Error:<BR> $_" -HaltScript}
}
}
#EndRegion

About mell9185

IT proffesional. Tech, video game, anime, and punk aficionado.
This entry was posted in Email, EWS, Exchange. Bookmark the permalink.

Leave a Reply