1

I have trying to extract attachments from Outlook which are matching the wildcard of senderemailaddress attribute. As can be seen in the below code, I was trying out with two filters but to no avail.

When I use uncommented filter currently active in the code, the code doesn't throw any errors nor does it download the attachments matching the test case. However if I activate the commented filter and run it, I get the following error.

Exception calling "Restrict" with "1" argument(s): "Cannot parse condition. Error at
"like"."
At C:\Users\acer\Desktop\outlook.ps1:42 char:2
+  $filteredItems = $folder.items.Restrict($filter)
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ComMethodTargetInvocation

Code:

$filepath = "C:\folder\subfolder\subsubfolder\"
function downloadFiles {
    $filter = "[UnRead]=true AND [SenderEmailAddress] -match @example"
    #$filter = "[UnRead]=true AND [SenderEmailAddress] -like '*@example*'"

    Add-Type -Assembly "Microsoft.Office.Interop.Outlook" | Out-Null
    $olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]

    $outlook = New-Object -ComObject Outlook.Application 
    $namespace = $outlook.GetNameSpace("MAPI")

    $folder = $namespace.GetDefaultFolder($olFolders::olFolderInBox)

    #$folder.Items | select SenderEmailAddress

    $filteredItems = $folder.Items.Restrict($filter)

    foreach ($objMessage in $filteredItems) {
        $intCount = $objMessage.Attachments.Count

        if ($intCount -gt 0) {
            for ($i=1; $i -le $intCount; $i++) {
                $objMessage.Attachments.Item($i).SaveAsFile($filepath+$objMessage.Attachments.Item($i).FileName)
            }
        }
        $objMessage.Unread = $false
    }

    $outlook.Close
}

downloadFiles

Edit1 : Thanks everyone for the suggestions. I was able to do it by filtering with unread = true and pattern matching the senderemailaddress from the properties of the filtered mails.

Adding the modified code:

$filepath = "C:\folder\subfolder\subsubfolder\"
    function downloadFiles {
        $filter="[UnRead]=true"
    $emailfilter = "*@xyz.co.in"
    $subjectfilter = "test file*"

    Add-Type -Assembly "Microsoft.Office.Interop.Outlook" | Out-Null
        $olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]

        $outlook = New-Object -ComObject Outlook.Application 
        $namespace = $outlook.GetNameSpace("MAPI")

        $folder = $namespace.GetDefaultFolder($olFolders::olFolderInBox)

        #$folder.Items | select SenderEmailAddress

        $filteredItems = $folder.Items.Restrict($filter)

        foreach ($objMessage in $filteredItems) {
            $subject = $objMessage.Subject
            $emailaddress = $objMessage.SenderEmailAddress

            if(($emailaddress -like $emailfilter) -and ($subject -like $subjectfilter)){
            $intCount = $objMessage.Attachments.Count

            if ($intCount -gt 0) {
                for ($i=1; $i -le $intCount; $i++) {
                    $objMessage.Attachments.Item($i).SaveAsFile($filepath+$objMessage.Attachments.Item($i).FileName)
                }
            }
            $objMessage.Unread = $false
          }

           else {continue}
       }
        $outlook.Close
    }

    downloadFiles

Now the problem is scheduling this script? When I run this script using the powershell path in command prompt it's working fine. But when I schedule the same it's not completing. I could see the outlook process generated by the task scheduer in TaskManager and have to manually kill the process to terminate the same. Any ideas?

abhi v
  • 13
  • 6

2 Answers2

0

The provider does not allow the use of Like in the filter for this method. From this MSDN article:

There is no way to perform a "contains" operation. For example, you cannot use Find or Restrict to search for items that have a particular word in the Subject field. Instead, you can use the AdvancedSearch method, or you can loop through all of the items in the folder and use the InStr function to perform a search within a field.

BenH
  • 9,766
  • 1
  • 22
  • 35
  • But I was able to use contains when the string is a number. Example: if my sendermailaddress was this /O=xxxxx/OU=EXCHANGE ADMINISTRATIVE GROUP (FYDIBOHF23SPDLT)/CN=RECIPIENTS/CN=02DE69998DE7457BBC1CAA86E82818DA-lmnopqr I could use the filter -contains lmnopqr to do the same – abhi v Feb 14 '17 at 15:10
0

I'd use EWS. Saves having to allow programmatic access to Outlook.

Easiest way is to download from nuget. You can do this in PowerShell by first downloading nuget:

$sourceNugetExe = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
$targetNugetExe = "D:\Program Files\nuget\nuget.exe" # chaneg path to suit
Invoke-WebRequest $sourceNugetExe -OutFile $targetNugetExe
Set-Alias nuget $targetNugetExe -Scope Global -Verbose

Then download the EWS nuget package:

Set-Location D:\Temp # change to suit
nuget install 'Microsoft.Exchange.WebServices'

Now you can start using :)

# load the assembly
[void][Reflection.Assembly]::LoadFile("D:\Temp\Microsoft.Exchange.WebServices.2.2\lib\40\Microsoft.Exchange.WebServices.dll")

# set ref to exchange - may need to change the version
$s = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2)

# replace with your email address
$email = "your.email@domain.com"

# grab your own credentials
$s.UseDefaultCredentials = $true

# discover the url from your email address
$s.AutodiscoverUrl($email)

# get a handle to the inbox
$inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($s,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)

#create a property set (to let us access the body & other details not available from the FindItems call)
$psPropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text

$items = $inbox.FindItems(100) # change to suit

# loop through the emails - at this point, we don't have full info - we have to Load the email
# restrict on what we do know - if the email is read and if it has attachments
$items | where { !$_.IsRead -and $_.HasAttachments } | ForEach-Object {
  # load the email, so we can get to other properties, like attachments, sender, etc
  $_.Load()
  # does the sender match our wildcard?
  if ($_.Sender -like '*lmnopqr*') {
    # loop through all file attachments
    $_.Attachments | Where-Object { $_ -is [Microsoft.Exchange.WebServices.Data.FileAttachment] } | ForEach-Object {
      # save them (yes, Load = Save in this instance!)
      $_.Load("D:\Temp\$($_.Name)")
    }
  }
}

See the EWS link for more info on how to interact with EWS.

Also, see my other SO post which is out of date for where to get the EWS assembly from, but does have some useful info on extra EWS methods/properties. It also has details on how to use alternative credentials, if you're not using your own (or the process runing PowerShell doesn't have an Exchange account).

Community
  • 1
  • 1
TechSpud
  • 3,418
  • 1
  • 27
  • 35
  • I am unable to access isread property in the script even though it's showing up in the list of object properties. Ant suggestions – abhi v Feb 15 '17 at 13:29
  • What does `$items[0].IsRead` yield? – TechSpud Feb 15 '17 at 14:00
  • `$items` does not have a property IsRead. an `item` does. To get to an item, to test, try this: `$item = $items | select -First 1; $item.IsRead` – TechSpud Feb 15 '17 at 14:47
  • It works like a charm. Thank you. One more benefit with this way of accessing mailbox is, it can be scheduled via task scheduler that runs successfully. One question, does this method require outlook to be installed in the client machine and also how can I access mail box of another user if I am logged in on this client – abhi v Feb 15 '17 at 14:52
  • Cool. Exactly. Takes the Outlook client out of the equation and is developed (and now open-sourced) by Microsoft. Can you mark me as the answer, if it's helped you, please? – TechSpud Feb 15 '17 at 14:54
  • No, Outlook doesn't need to be installed. The EWS assembly does all the connecting. To connect to another mailbox, you could wrap the above code into a function with params, then call the function with account details. See my other post, linked in my answer, on how to connect using different credentials. – TechSpud Feb 15 '17 at 14:57