Share via


Powershell script to get all new attachments and store them on a network share

Question

Tuesday, June 26, 2012 10:38 AM

I need help to create a script that reads a users inbox and stores the .pdf attachments received on a network share.

I have tried to use the EWS service, but with the latest SP i get bind errors.

Is it possible to use the export-mailbox cmdlet to save to a network share instead of a pst file? If not, is there a way to extract the attachments from the pst file?

All replies (20)

Wednesday, June 27, 2012 11:53 AM ✅Answered | 1 vote

Found a solution with powershell and EWS, with a little help from:

http://gsexdev.blogspot.no/2012/01/ews-managed-api-and-powershell-how-to.html#!/2012/01/ews-managed-api-and-powershell-how-to.html

http://gsexdev.blogspot.no/2012/02/ews-managed-api-and-powershell-how-to_22.html#!/2012/02/ews-managed-api-and-powershell-how-to_22.html

http://poshcode.org/624

The script binds to a mailbox, finds all attachments in "Inbox" and then moves all items from "Inbox" to a subfolder "PROCESSED".

The script must be run as a user with Full Manage Access, or impersonation must be added to the script.

################################################
#
#Accept any certificates presented by the CAS
#
################################################

## Create a compilation environment
$Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
$Compiler=$Provider.CreateCompiler()
$Params=New-Object System.CodeDom.Compiler.CompilerParameters
$Params.GenerateExecutable=$False
$Params.GenerateInMemory=$True
$Params.IncludeDebugInformation=$False
$Params.ReferencedAssemblies.Add("System.DLL") | Out-Null

$TASource=@'
  namespace Local.ToolkitExtensions.Net.CertificatePolicy{
    public class TrustAll : System.Net.ICertificatePolicy {
      public TrustAll() { 
      }
      public bool CheckValidationResult(System.Net.ServicePoint sp,
        System.Security.Cryptography.X509Certificates.X509Certificate cert, 
        System.Net.WebRequest req, int problem) {
        return true;
      }
    }
  }
'@ 
$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
$TAAssembly=$TAResults.CompiledAssembly

## We now create an instance of the TrustAll and attach it to the ServicePointManager
$TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
[System.Net.ServicePointManager]::CertificatePolicy=$TrustAll

################################################
#
#Load the EWS API and connect to the CAS/EWS
# EWS API is found at: http://www.microsoft.com/en-us/download/details.aspx?id=28952
#
################################################

## Load Managed API dll
Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"

## Set Exchange Version (Exchange2010, Exchange2010_SP1 or Exchange2010_SP2)
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2

## Create Exchange Service Object
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)

## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials
#Credentials Option 1 using UPN for the windows Account
$creds = New-Object System.Net.NetworkCredential("USERNAME","PASSWORD","DOMAIN") 
$service.Credentials = $creds  

#Credentials Option 2
#service.UseDefaultCredentials = $true

## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use

$MailboxName = "user@domain.com"

#CAS URL Option 1 Autodiscover
#$service.AutodiscoverUrl($MailboxName,{$true})
#"Using CAS Server : " + $Service.url 

#CAS URL Option 2 Hardcoded  
$uri=[system.URI] "https://<FQDN>/ews/Exchange.asmx"
$service.Url = $uri  


#Bind to the Inbox folder
$Sfha = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::HasAttachments, $true)
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,"user@domain.com")   
$Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  

####################################################################################################
#
#This section finds attachments and copies the attachment to the download directory
#
####################################################################################################


$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(100)
$downloadDirectory = "directory name, UNC will work"

$findItemsResults = $Inbox.FindItems($Sfha,$ivItemView)
foreach($miMailItems in $findItemsResults.Items){
    $miMailItems.Load()
    foreach($attach in $miMailItems.Attachments){
        $attach.Load()
        $fiFile = new-object System.IO.FileStream(($downloadDirectory + “\” + $attach.Name.ToString()), [System.IO.FileMode]::Create)
        $fiFile.Write($attach.Content, 0, $attach.Content.Length)
        $fiFile.Close()
        write-host "Downloaded Attachment : " + (($downloadDirectory + “\” + $attach.Name.ToString()))
    }
}

####################################################################################################
#
#This section moves emails from the Inbox to a subfolder of "Inbox" called "PROCESSED", make sure to 
#create the folder.
#
####################################################################################################

#Get the ID of the folder to move to  
$fvFolderView =  New-Object Microsoft.Exchange.WebServices.Data.FolderView(100)  
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow;
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,"PROCESSED")
$findFolderResults = $Inbox.FindFolders($SfSearchFilter,$fvFolderView)  
  
#Define ItemView to retrive just 100 Items    
$ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(100)  
$fiItems = $null    
do{    
    $fiItems = $Inbox.FindItems($Sfha,$ivItemView)   
    #[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)  
        foreach($Item in $fiItems.Items){      
            # Copy the Message  
            #$Item.Copy($findFolderResults.Folders[0].Id)  
            # Move the Message  
            $Item.Move($findFolderResults.Folders[0].Id)  
        }    
        $ivItemView.Offset += $fiItems.Items.Count    
    }while($fiItems.MoreAvailable -eq $true)  

Friday, July 4, 2014 9:43 AM ✅Answered

I got it. It's not that easy.

You have to replace

      write-host "Downloaded Attachment : " + (($downloadDirectory + “\” + $attach.Name.ToString()))
    }
}

####################################################################################################
#
#This section moves emails from the Inbox to a subfolder of "Inbox" called "PROCESSED", make sure to 
#create the folder.
#
####################################################################################################

#Get the ID of the folder to move to  
$fvFolderView =  New-Object Microsoft.Exchange.WebServices.Data.FolderView(100)  
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow;
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,"PROCESSED")
$findFolderResults = $Inbox.FindFolders($SfSearchFilter,$fvFolderView)  
  
#Define ItemView to retrive just 100 Items    
$ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(100)  
$fiItems = $null    
do{    
    $fiItems = $Inbox.FindItems($Sfha,$ivItemView)   
    #[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)  
        foreach($Item in $fiItems.Items){      
            # Copy the Message  
            #$Item.Copy($findFolderResults.Folders[0].Id)  
            # Move the Message  
            $Item.Move($findFolderResults.Folders[0].Id)  
        }    
        $ivItemView.Offset += $fiItems.Items.Count    
    }while($fiItems.MoreAvailable -eq $true)  

with

        write-host "Downloaded Attachment : " + (($downloadDirectory + “\” + $attach.Name.ToString()))
    }
    $miMailItems.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
}

(for permanently delete the message/s)

or

        write-host "Downloaded Attachment : " + (($downloadDirectory + “\” + $attach.Name.ToString()))
    }
    $miMailItems.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::MoveToDeletedItems)
}

(for moving message/s to Trash/Deleted Items)


Friday, July 4, 2014 10:30 AM ✅Answered

Or you could use:

$Item.Delete(1)

(Forgot the "1" in my last post, this time i tested it.)


Tuesday, January 27, 2015 4:26 PM ✅Answered

OK I have managed to do it with a lot of help from the above examples and some trile and error

the problem is by the time the code is in the last foreach loop you have lost most of the mail info so you have to add this back in be for the loop  below is the code that worked for me

I alos have added a sequence generator in to the file name as well this is the $a value

 

$findItemsResults = $Inbox.FindItems($Sfha,$ivItemView)
foreach($miMailItems in $findItemsResults.Items){
    $miMailItems.Load()
    $fromAddress = $miMailItems.From
    foreach($attach in $miMailItems.Attachments){
        $mailFrom = $fromAddress.Address
        $a = (Get-Date -Format yyyyMMddHHmmssfff).ToString()
        $attach.Load()
        $fiFile = new-object System.IO.FileStream(($downloadDirectory + “\” + $a+"_"+$mailFrom+"#"+$attach.Name.ToString() -replace " ","-"), [System.IO.FileMode]::Create)
        $fiFile.Write($attach.Content, 0, $attach.Content.Length)
        $fiFile.Close()
        write-host "Downloaded Attachment : " + (($downloadDirectory + “\” + $a+"_"+$mailFrom+"#"+$attach.Name.ToString() -replace " ","-"))
    }
}

Tuesday, June 26, 2012 11:06 AM

export-mailbox is no longer present since EX2010 SP1. It's replaced by New-MailboxExportRequest that, to my knowledge can only export to .pst. I think it's also not possible to strip the files from the source mailbox, if that's what you want. Reference: http://technet.microsoft.com/en-us/library/ff607299

Extracting attachments from outlook can be done with http://www.nirsoft.net/utils/outlook_attachment.html .


Tuesday, June 26, 2012 11:40 AM

You can use a script like this, will export the pdfs in inbox folder (or specified) to remote UNC share with attachment name.

#Extract PDF From Inbox
##########################################################
#Variables to modify $folder & $filepath if you consider
$object = New-Object -comobject outlook.application
$namespace = $object.GetNamespace("MAPI")
$folder = $namespace.GetDefaultFolder(6)
$globalpath = "\\server\sharename\" 
$username = get-content env:username
$pdffolder = $globalpath + $username


##########################################################
#Extractor
new-item -type directory -path $pdffolder
$folder.Items | foreach {$SendName = $_.SenderName
    $_.attachments | foreach {
        Write-Host $_.filename #Just for testing is reading, remove it after linking to GPO 
        $attachName = $_.filename
        If ($attachName.Contains("pdf")) {
        $_.saveasfile((Join-Path $pdffolder $attachName))
   }
  }
}

Tets it before, if works as expected for you link the ps1 file to a GPO user script section. Remember domain users will require write access to detination share as they create \UNC\share\username\ structure and write the pdf files.

regards

Julio Rosua


Wednesday, June 27, 2012 8:46 AM

I didn’t find any command that can help you to export the attachments out.

I suggest you ask your question in this forum, maybe somebody there can help you on this issue:
http://social.technet.microsoft.com/Forums/da-dk/exchangesvrdevelopment/threads

Thanks,

Evan

Evan Liu

TechNet Community Support


Friday, February 22, 2013 12:51 AM

@Ander_s

Your code helped a lot.

I was wondering if there was a way to save the attachments with their name and have their original date time stamp added at the end of the name?

If I could also have the files retain their date time attribute as I will then move them to a folder with their date and time stamp that gets automatically created.

I have a few emails coming in at a time and the attachments have the same name.

Any help will be much appreciated.


Wednesday, March 13, 2013 1:21 PM

To add the original timestamp to the filename replace this line:

$fiFile = new-object System.IO.FileStream(($downloadDirectory + “\” + $attach.Name.ToString()), [System.IO.FileMode]::Create)
    

With one of the following:

1. For a timestamp at the beginning of the filename:

$timestamp = $attach.LastModifiedTime.ToString().substring(0,10).replace(".","")
$fiFile = new-object System.IO.FileStream(($downloadDirectory + “\” + $timestamp + $attach.Name.ToString()), [System.IO.FileMode]::Create)     

2. Timestamp at the end of the filename:

$timestamp = $attach.LastModifiedTime.ToString().substring(0,10).replace(".","")
$fiFile = new-object System.IO.FileStream(($downloadDirectory + “\”  + $attach.Name.ToString().replace(".", $timestamp +".")), [System.IO.FileMode]::Create) 

To change the Date Modified to the original Date Modified you can play with the $attach variable and get the time with $attach.LastModifiedTime. Il look into it when i have time.


Wednesday, June 5, 2013 5:39 PM

This script is really nice! I have the same issue but with public folders. Would it be possible to modify this script to get attachments out of a public folder?


Wednesday, May 14, 2014 9:27 PM

I also need to use this script on a public folder. Looking at other examples, I tried to use Get-PublicFolder to get the folder id of the folder of interest. 

I get the message "The term 'GetPublicFolder' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included. verify that the path is correct and try again."

I've tried various syntax's of the command, including simply Get-PublicFolder with no arguments since I am not sure I am specifying the folder path correctly. I have also tried Get-MailPublicFolder, due to another mention.

Could somebody please post advice on how to modify this script to operate on a public folder?

Ian Smith


Thursday, July 3, 2014 1:04 PM

Thank you for sharing that script.

Is there a way to delete the messages instead of move them to another folder?


Thursday, July 3, 2014 1:20 PM

You could probably just change the code from move to delete like this:

$Item.Move($findFolderResults.Folders[0].Id)

to:

$Item.Delete()

Havent tested it, but you could just check the Methods for $Item and you will have an answer :)


Friday, July 4, 2014 7:57 AM

I tried this, but it does simply nothing :-(

Attachemnts are still downloaed, but not moved.


Tuesday, January 27, 2015 3:20 PM

Hi I love the script and have found it really useful but can any one work out how to add the senders name in to the downloaded file name as well


Thursday, October 22, 2015 9:00 AM

I had to change this slightly with my setup:

$timestamp = $attach.LastModifiedTime.ToString().substring(0,10).replace("/","")

as the date was formatted dd/mm/yyyy


Tuesday, November 3, 2015 9:04 AM

Hello, is there any way to maintain logs for whatever files are moved to Process Directory.

This script is working fine, but we have noticed some of the attachments doesnt move to my network folder but emails gets moved to Process directory. Hence need to know whats going wrong.


Wednesday, April 5, 2017 7:12 PM

We have the same problem here.

Sometimes the attachment is not copied to the folder.

Is there anyone who has a solution for !?!?!?


Thursday, August 16, 2018 9:17 AM

I got this script working, great work.

I only have 1 small request.

When the detachment is copied to the download directory it gets a new creation date.

Is there an option to save the file with the original creation date?


Friday, August 17, 2018 11:17 AM

I got this script working, great work.

I only have 1 small request.

When the detachment is copied to the download directory it gets a new creation date.

Is there an option to save the file with the original creation date?

Also another question.

Is it possible to only export / download the PDF files?