How to loop through all task folders in Outlook? - vba

This Outlook VBA code loops through all the folders and debugs their names.
I can get the default folder for tasks
Set ObjTaskFolder = ObjNS.GetDefaultFolder(olFolderTasks)
I have two task folders and I need all the items (Tasks) within these folders.
I don't want to check all the folder items with .class=oltask so I am looking for a way to evaluate at folder level.
Set ObjNS = Application.GetNamespace("MAPI")
For f = 1 To ObjNS.Folders(1).Folders.Count
Debug.Print ObjNS.Folders(1).Folders(f)
If (ObjNS.Folders(1).Folders(f).Class = olTask) Then
'Task folder
End If
Next f
I am looking for a way to evaluate folders, for the task folder only and then loop through all the items in within?

Check that the MAPIFolder.DefaultItemType property is OlItemType.olTaskItem (=3) or the MAPIFolder.DefaultMessageClass property is "IPM.Task".

Related

VBA Loop Through Emails in Folder in Order

I am trying to loop over the emails in folder, download the attachments and move the message to a folder labelled "processed." I want every email in the folder to the Processed folder and only keep the attachment from the most recent email. I figured the easiest way to do this would be to just loop through them all and have the most recent go last, overwriting the attachment each time.
Code Snippet Below
oOlInbNo.Items.Sort "[ReceivedTime]", True
Do While oOlInbNo.Items.count > 0
For Each MailItem In oOlInbNo.Items
For Each atch In MailItem.Attachments
If Right(atch.Filename, 3) = "xls" Then
atch.SaveAsFile NewFileNameNo
End If
Next atch
MailItem.Move oOlInbNo.Folders("Processed")
Next MailItem
Loop
However, when I run this it processes emails in this order
August 31
September 6
September 1
September 2
I cannot figure out why Sept 6 is going out of order.
Sort items not folder.items.
Set oOlInbNoItems = oOlInbNo.items
oOlInbNoItems.Sort "[ReceivedTime]", True
You may have to switch from For Each to a reverse For Next.
Option Explicit
Private Sub MoveItems_OldestFirst()
Dim oOlInbNo As Folder
Dim oOlInbNoItems As items
Dim oOlItem As Object
Dim oOlMailitem As MailItem
Dim atch As Attachment
Dim i As Long
Set oOlInbNo = Session.GetDefaultFolder(olFolderInbox)
Set oOlInbNoItems = oOlInbNo.items
oOlInbNoItems.Sort "[ReceivedTime]", True
If oOlInbNoItems.Count > 0 Then
For i = oOlInbNoItems.Count To 1 Step -1
If oOlInbNoItems(i).Class = olMail Then
Set oOlMailitem = oOlInbNoItems(i)
For Each atch In oOlMailitem.Attachments
If Right(atch.fileName, 3) = "xls" Then
atch.SaveAsFile NewFileNameNo
End If
Next atch
'Debug.Print oOlMailitem.ReceivedTime
oOlMailitem.Move oOlInbNo.Folders("Processed")
End If
Next
End If
End Sub
Each time you call the Items property of Outlook folders you get a new Items collection with an individual sorting order. You need to deal with the same collection instance if you want to preserve the order.
Dim folderItems as Outlook.Items
Set folderItems = oOlInbNo.Items
folderItems.Sort "[ReceivedTime]", False
Do While folderItems.count > 0
If folderItems.Item(0).Class = olMail Then
Set oOlMailitem = folderItems.Item(0)
As you can see there is no need to have two loops - you can use a single one instead.
I figured the easiest way to do this would be to just loop through them all and have the most recent go last, overwriting the attachment each time.
Looping through all items in the folder is not really a good idea. You may better process items in small bunches, so you could leave the Outlook UI responsive.
Also you may consider using the Find/FindNext or Restrict methods of the Items class helpful. For example, you could find recent items with attachments and process only them for saving attached files on the disk. Other items you can simply move to another folder if required.
Read more about these methods in the articles I wrote for the technical blog:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder

Looping through specific subfolders in outlook containing a specific string

I want to look for specific items inside specific subfolders in Outlook (macro VBA) that can be in first or second level subfolder, however I cannot make it to work. I have found other questions that loop through all the items in all folders, but not that go through all items in specific subfolders.
fldrname = "Clearing"
Set objNS = GetNamespace("MAPI")
Set ClearingFolders = Outlook.Folders("Clearing", objNS.Folders)
For Each ClearingFolders In objParentFolderCollection
For Each folder In ClearingFolders
If InStr(1, fldrname, folder.Name, vbTextCompare) > 0 Then
{findEmail}
End If
Next folder`
Thanks for your help!
The code below demonstrates how to access every mail item within every folder, and sub-folder to any depth, within a parent folder. It does this by outputting an indented list of items and sub-folders to the Immediate Window. The format of the output is:
ParentFolderName
Date Subject (of mail item within ParentFolder
Date Subject (of mail item within ParentFolder
Date Subject (of mail item within ParentFolder
ChildFolder1Name
Date Subject (of mail item within ChildFolder1Name
Date Subject (of mail item within ChildFolder1Name
GrandchildFolder1Name
Date Subject (of mail item within GrandchildFolder1Name
Date Subject (of mail item within GrandchildFolder1Name
ChildFolder2Name
Date Subject (of mail item within ChildFolder2Name
Date Subject (of mail item within ChildFolder2Name
GrandchildFolder2Name
Date Subject (of mail item within GrandchildFolder2Name
Date Subject (of mail item within GrandchildFolder2Name
GreatgrandchildFolder1Name
Date Subject (of mail item within GreatgrandchildFolder1Name
Date Subject (of mail item within GreatgrandchildFolder1Name
ChildFolder3Name
: : : : :
There are statements within your code I do not understand so I have ignored your code and created my own.
Consider first:
Set Fldr = Session.Folders("StoreName").Folders("TopLevelFolderName")
Your equivalent of this statement is:
Set objNS = GetNamespace("MAPI")
Set Fldr = objNS.Folders("StoreName").Folders("TopLevelFolderName")
With VBA there is often more than one way of achieving the same effect. I prefer Session to objNS. My code so my favourites. Change to your favourite if you wish.
A store is a file on disc that Outlook uses to hold mail items, tasks, appointment items and so on. I assume “Clearing” is the name of a folder and not the name of a store. Your folder pane will look something like this:
StoreName1
Clearing1
Deleted Items
Inbox
Sent Items
StoreName2
Inbox
Clearing2
Sent
Trash
You can have as many stores as you wish. There will be one per email address and perhaps one for archives. When I change computers, I add my old stores to my new Outlook installation, so I have access to all my old emails.
It seems there is always an “Inbox”. Other standard folders change their names from version to version so you might have “Deleted Items” or “Trash” or something else. You can add your own folders wherever you like.
If your “Clearing” is a store, you will need:
Set Fldr = Session.Folders("Clearing")
If your “Clearing” is at the same level as “Inbox” like my “Clearing1”, you will need:
Set Fldr = Session.Folders("StoreName1").Folders("Clearing1")
If your “Clearing” is under “Inbox” like my “Clearing2”, you will need:
Set Fldr = Session.Folders("StoreName2").Folders("Inbox").Folders("Clearing2")
Change my statement to match your system.
Notice that I write:
Dim Fldr As Outlook.Folder
but
Dim ItemCrnt As MailItem
This code runs under Outlook so I do not need to specific Outlook. I could have written Outlook.MailItem but it would not add value because VBA only has one data type named MailItem. However, Outlook as two data types Folder; one for disc folders and one for Outlook folders. Outlook VBA will assume you mean Outlook.Folder when you write Folder but I once got myself into a muddle when I did not specify which Folder I meant. Now, I am always careful to write Outlook.Folder or Scripting.Folder so I will not forget when it is important.
The sub ProcessChild is recursive. There are excellent explanations of recursion on the web so I will not attempt my own explanation now. However, if you are confused, I will add an explanation of my routine.
Now consider:
For InxI = 1 To FldrPrnt.Items.Count
: : :
For InxF = 1 To FldrPrnt.Folders.Count
You have used For Each. I sometimes use For Each but I find For Index more convenient most of the time.
FldrPrnt is the folder whose mail items and sub-folders I wish to access. FldrPrnt.Items gives me access to the items and FldrPrnt.Folders gives me access to the sub-folders.
When I write For InxI = 1 To FldrPrnt.Items.Count, I access the items oldest first. If I had written For InxI = FldrPrnt.Items.Count to 1 Step -1, I would have accessed the items newest first. “Oldest” and “Newest” here does not refer to the date of the item. It refers to the order in which items were added to FldrPrnt.Items. Normally mail items are added in date order so these two orders are the same. However, if you accidentally delete an old mail item then move it back from folder “Deleted Items”, it will become the newest item in the folder.
Often you can write either For InxI = 1 To FldrPrnt.Items.Count or For InxI = FldrPrnt.Items.Count to 1 Step -1. However, if your processing involves moving items to another folder, you must use FldrPrnt.Items.Count to 1 Step -1. With For Index, you are identifying items by their position within FldrPrnt.Items. If you move item 20 to another folder, item 21 becomes item 20, item 22 becomes item 21 and so on. For the next repeat of the loop, you will check the new item 21 not the old item 21. We sometimes get questions where someone is only checking half their items. This is the reason.
Notice If TypeName(FldrPrnt.Items(InxI)) = "MailItem" Then. Not every item is a MailItem. It is essential to check an item’s type before processing it since different items have different properties.
I hope the above, is enough for you to understand my code but ask question is necessary. All my code does is display the received time and subject of each mail item. You will have to replace my Debug.Print statement with whatever code you need to achieve your objectives.
Option Explicit
Sub Main()
Dim Fldr As Outlook.Folder
Set Fldr = Session.Folders("StoreName").Folders("TopLevelFolderName")
Call ProcessChild(Fldr, 0)
End Sub
Sub ProcessChild(ByRef FldrPrnt As Outlook.Folder, ByVal Indent As Long)
Dim InxF As Long
Dim InxI As Long
Dim ItemCrnt As MailItem
Debug.Print Space(Indent * 2) & FldrPrnt.Name
For InxI = 1 To FldrPrnt.Items.Count
If TypeName(FldrPrnt.Items(InxI)) = "MailItem" Then
Set ItemCrnt = FldrPrnt.Items(InxI)
With ItemCrnt
Debug.Print Space(Indent * 2 + 2) & .ReceivedTime & " " & .Subject
End With
End If
Next
For InxF = 1 To FldrPrnt.Folders.Count
If FldrPrnt.Folders(InxF).DefaultItemType = olMailItem Then
Call ProcessChild(FldrPrnt.Folders(InxF), Indent + 1)
End If
Next
End Sub
You need to iterate over all folders recursively:
Private Sub processFolder(ByVal oParent As Outlook.MAPIFolder)
Dim oFolder As Outlook.MAPIFolder
Dim oMail As Outlook.MailItem
For Each oMail In oParent.Items
'Get your data here ...
Next
If (oParent.Folders.Count > 0) Then
For Each oFolder In oParent.Folders
processFolder oFolder
Next
End If
End Sub
Also, you may consider using the AdvancedSearch method of the Application class which performs a search based on a specified DAV Searching and Locating (DASL) search string. The key benefits of using the AdvancedSearch method in Outlook are:
The search is performed in another thread. You don’t need to run another thread manually since the AdvancedSearch method runs it automatically in the background.
Possibility to search for any item types: mail, appointment, calendar, notes etc. in any location, i.e. beyond the scope of a certain folder. The Restrict and Find/FindNext methods can be applied to a particular Items collection (see the Items property of the Folder class in Outlook).
Full support for DASL queries (custom properties can be used for searching too). You can read more about this in the Filtering article in MSDN. To improve the search performance, Instant Search keywords can be used if Instant Search is enabled for the store (see the IsInstantSearchEnabled property of the Store class).
You can stop the search process at any moment using the Stop method of the Search class.
Read more about the AdvancedSearch method in the Advanced search in Outlook programmatically: C#, VB.NET article.
So, maybe you don't need to iterate over all folders and search for specific items there any longer?

Outlook VBA - Bulk Move of Email Between Subfolders

I routinely have to move a decent amount of email (150+) from a subfolder to another. There are many folders in the mailbox that I perform this task on. It seems like it would be an easy macro to write, but what I have is substantially slower than doing a Ctrl+A, drag to destination folder.
I have reviewed previous questions about moving Outlook emails and Microsoft's documentation, but I am unable to figure out how to accomplish moving the emails in a a fast and reliable manner. I would appreciate any information on where I am going wrong and if there is another solution besides VBA.
My current code is below. My end goal would be to loop through a list of folder names (instead of me selecting the folder).
Thanks in advance.
Sub MoveEmailsToDone()
On Error Resume Next
Dim ns As Outlook.NameSpace
Dim AnalystFolder As Outlook.MAPIFolder
Dim MoveToFolder As Outlook.MAPIFolder
Set ns = Application.GetNamespace("MAPI")
Set AnalystFolder = Application.ActiveExplorer.CurrentFolder
Set MoveToFolder = ns.Folders("username#domain.com").Folders(AnalystFolder.Name & "-DONE")
For i = AnalystFolder.Items.Count To 1 Step -1
AnalystFolder.Items(i).Move MoveToFolder
Next i
Set ns = Nothing
Set AnalystFolder = Nothing
Set MoveToFolder = Nothing
End Sub
From experience Move and Delete are slow.
http://computer-programming-forum.com/1-vba/17216b85e9c096d3.htm
07 Jul 2003
The following code loops through each mail item in a specified folder
and moves the item to another folder. For 1100 items, it takes more
than 5 min. It doesn't move that slow when I select all and move in
the user interface.
.
Outlook uses Extended MAPI to implement a move operation, namely
IMAPIFolder::CopyMessages() which takes a list of entryids, hence it does not
need to open each message. Store provider completes the whole operation on the
server without sending lots of data back and forth as apparently happens when
you run your code.
Dmitry Streblechenko
https://stackoverflow.com/users/332059/dmitry-streblechenko
DoEvents lets you use Outlook while the code runs.
For i = AnalystFolder.Items.Count To 1 Step -1
DoEvents
AnalystFolder.Items(i).Move MoveToFolder
Next i
MsgBox "MoveEmailsToDone is finally done."

VBA Outlook Macro: How many E-Mails does a Folder contain?

Goal: an Outlook-VBA macro thas looks through every available folder and checks if it contains a mail message or not. If not, the user can decide to delete the folder. Is there any way to do this?
Sub findAndDeleteEmptyFolders()
Dim Folders As Outlook.Folders
Dim F As Outlook.MAPIFolder
Dim FoundMail As Boolean
Set Folders = Application.Session.Folders
For Each F In Folders
Dim FItems As Integer
FItems = F.Items.count
MsgBox ("Der Ordner: " & F.Name & " hat " & FItems & " Items")
Next
End Sub
So this code just looking into the top folders, but not into the folders like "Inbox". It gives out that every active mail account has 0 Mails, but there are like 9000 Mails in some of them...how can I look, lets say, deeper into the folders (subfolders?).
Iterating over all folders and items in Outlook is not a good idea. If you need to find items that satisfy to your conditions you can use the Find/FindNext or Restrict methods. The following articles describe them in depth:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
But if you need to find items in all folders I'd suggest using the AdvancedSearch method of the Application class. See Advanced search in Outlook programmatically: C#, VB.NET for more information.

Delete a mailitem permanently in outlook

I'm trying to delete a mailitem using the outlook API. Like the following,
Dim objMail
For each objMail in objFolder.Items
objMail.Delete
Next
Obviously, deleting an item straight away is to simple. Outlook just move it into the "Deleted Items" folder instead of deleting it. I tried to get the "Deleted Items" folder using
OutlookNameSpace.GetDefaultFolder(olDeletedItems)
and delete the mail again, but the PST the code is working on is not the default mailbox and the folder returned is the wrong deleted items folder. How can I permanently delete this mailitem?
I tried to loop through all folders in the current store, but there's no way of telling which folder is the deleted items folder except by comparing names, I can't do that since the programs will be used in multiple languages and the name is different for each version.
PS: I cannot use a third party dll :(
Help!
First problem of your code is not appropriate loop you use. If you want to delete (almost anything in VBA) you need to loop your collection from the last element to first. If not, you change the order of the collection- after you delete 1st element >> 2nd one is moved to 1st position and will not be deleted.
Therefore this code should delete all items from your DeltetedItems folder:
Sub Delete_all_from_dust_bin()
Dim myFolder As Outlook.Folder
Set myFolder = Application.GetNamespace("MAPI"). _
GetDefaultFolder(olFolderDeletedItems)
Dim i As Long
For i = myFolder.items.Count To 1 Step -1
myFolder.items(i).Delete
Next i
End Sub
Obviously, you could prepare similar code for deleting from any other folder. You will run both deletion loops to remove items for sure.
Some additional remarks for MailItem.Delete Method from MSDN:
The Delete mothod deletes a single item in a collection. To delete all
items in the Items collection of a folder, you must delete each item
starting with the last item in the folder. For example, in the items
collection of a folder, AllItems, if there are n number of items in
the folder, start deleting the item at AllItems.Item(n), decrementing
the index each time until you delete AllItems.Item(1).
Edit due to some comments from OP.
Even if you need to delete some items (not all) remember to use the loop type I presented above.
If you need to refer to any other DeletedItems folder in other stores you can find this folder in these ways:
'with index reference
Application.GetNamespace("MAPI").Stores(2).getdefaultfolder(olFolderDeletedItems)
'with name reference
Application.GetNamespace("MAPI").Stores("Business Mail").getdefaultfolder(olFolderDeletedItems)
I don't know if this works with all Outlook versions but it's working with Outlook 2010.
For reference purpose, here's the final method to permanently delete an item I found.
PS: The Migration ID is GUID previously stored for a bulletproof-way to track the item
Dim mailIndex
For mailIndex = objFolder.Items.Count To 1 Step - 1
Dim migrationProperty
Set migrationProperty = GetMigrationProperty(objFolder.Items(mailIndex).ItemProperties
if not migrationProperty is nothing Then
objFolder.Items(mailIndex).Delete
Call DeleteMailPermanently(migrationProperty.Value)
End if
Next
Function DeleteMailPermanently(strMailMigrationID)
Dim objDeletedMail, objDeletedMigrationProperty
Set m_objPSTDeletedItemsFolder
= GetDeletedItemsFolder(PSTStore, strMailMigrationID)
For Each objDeletedMail in m_objPSTDeletedItemsFolder.Items
Set objDeletedMigrationProperty
= GetMigrationProperty(objDeletedmail.ItemProperties)
if not objDeletedMigrationProperty is nothing
and objDeletedMigrationProperty.Value = strMailMigrationID then
objDeletedMail.Delete
Next
End Function
Function GetDeletedItemsFolder(objParentFolder, strMigrationID)
Dim objFolder, objMail
For each objMail in objFolder.Items
Dim migrationProperty
Set migrationProperty = GetMigrationProperty(objMail.ItemProperties)
If migrationProperty.Value = strMigrationID
Set GetDeletedItemsFolder = objFolder
Exit Function
End If
Next
if objFolder.Folders.Count >= 1 Then
Dim subFolder
Set subFolder = GetDeletedItemsFolder(objFolder, strMigrationID)
If not subFolder is Nothing Then
Set GetDeletedItemsFolder = subFolder
Exit Function
End If
Set GetDeletedItemsFolder = Nothing
End function
I had the same problem - my code wanted to delete appointment items as part of a sync, but this was clogging up the Deleted Items folder. But what I realised was - when you delete an object, all it's doing is moving it to Deleted Items. So just delete it twice! No need to worry about tracking properties, or clearing out the whole folder (which may be overkill).
EDIT: no, sorry, this doesn't work. Stupid Outlook. I tried using the Move method, and then deleting from Deleted Items, but it just puts it in the Drafts folder instead, bizarrely.
What seems to work is deleting an item, then deleting the last item in Deleted Items.
Here's a fragment of my code:
Set ns = Application.GetNamespace("MAPI")
Set delItemsFolder = ns.GetDefaultFolder(olFolderDeletedItems)
Set calItems = syncFolder.Items
For i = calItems.Count To 1 Step -1
calItems(i).Delete
delItemsFolder.Items.Item(delItemsFolder.Items.Count).Delete
Next
Also, if you need the Deleted Items folder from a store other than the default store. use Store.GetDefaultFolder instead of Namespace.GetDefaultFolder.
If you want to completely bypass the Deleted Items folder, you will need to either use Extended MAPI (C++ or Delphi only - IMAPIFolder::DeleteMessages) or Redemption (I am its author - any language - its RDOMail.Delete method allows to either permanently delete the message or move it to the Deleted Items folder).