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:
- 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
- It requires the EWS API, and you can specify the path to your installed version
- The Script searches for any email with a similar supplied subject
- Optionally the script can do the following:
- Do an exact search for the subject supplied
- Delete the email after a successful download of all the attachments
- Can be set not to overwrite files with the same name in the target directory
- If the attachments are zip files it can extract the files to the specified location (does not work with overwrite protection)
- Search for only emails matching today’s date
- 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 |