How would I cycle through an open Outlook windows using VBA? - vba

I often have multiple email items open, some that I composed that are not yet sent and others that I received that I didn't yet close but I am referencing.
What I would like to do is have a fast way to cycle through all of the open windows to find the one that I am looking for.
In Excel I created a macro to cycle through the tabs of an Excel document like this.
Sub PreviousSheet()
On Error Resume Next
ActiveSheet.Previous.Select
End Sub
Sub NextSheet()
On Error Resume Next
ActiveSheet.Next.Select
End Sub
In Outlook, how would I cycle through the open windows using VBA?
Update
Sub test()
Dim olApp As Outlook.Application
Set olApp = GetOutlookApp()
'I think this is how to loop through the open items?
For i = olApp.Inspectors.Count To 1 Step -1
Set olItem = olApp.Inspectors.Item(i).CurrentItem
olItem.Select 'How do I set focus?
Next i
End Sub
Function GetOutlookApp() As Outlook.Application
' returns reference to native Application object
Set GetOutlookApp = Outlook.Application
End Function

Sub GetPreviousOpenItem()
Set MainWindow = Application.ActiveExplorer
Dim olApp As Outlook.Application
Set olApp = GetOutlookApp()
If olApp.ActiveInspector Is Nothing Then
Exit Sub
End If
ActiveInspectorIndex = GetIndexOfActiveInspector(olApp, olApp.ActiveInspector)
If ActiveInspectorIndex - 1 > 0 Then
Dim PreviousInspector As Inspector
Set PreviousInspector = olApp.Inspectors(ActiveInspectorIndex - 1)
olApp.Inspectors(ActiveInspectorIndex - 1).Display
Else
olApp.Inspectors(olApp.Inspectors.Count).Display
End If
MainWindow.Activate
End Sub
Sub GetNextOpenItem()
Set MainWindow = Application.ActiveExplorer
Dim olApp As Outlook.Application
Set olApp = GetOutlookApp()
If olApp.ActiveInspector Is Nothing Then
Exit Sub
End If
ActiveInspectorIndex = GetIndexOfActiveInspector(olApp, olApp.ActiveInspector)
If ActiveInspectorIndex + 1 <= olApp.Inspectors.Count Then
Dim NextInspector As Inspector
Set NextInspector = olApp.Inspectors(ActiveInspectorIndex + 1)
NextInspector.Display
Else
olApp.Inspectors(1).Display
End If
MainWindow.Activate
End Sub
Function GetIndexOfActiveInspector(olApp, CurrentItem) As Integer
CurrentItem = olApp.ActiveInspector
For i = 1 To olApp.Inspectors.Count
Dim Inspector
Set Inspector = olApp.Inspectors.Item(i)
Set olItem = Inspector.CurrentItem
If olItem Is CurrentItem Then
GetIndexOfActiveInspector = i
Exit Function
End If
Next i
MainWindow.Activate
End Function
Function GetOutlookApp() As Outlook.Application
' returns reference to native Application object
Set GetOutlookApp = Outlook.Application
End Function

Related

Delete draft mail on close when not sent

I've got some draft mails with some buttons to copy and open them. Only a few values need to be filled in and then the mails will be sent. I want to keep the drafts. But if a mail is not sent, I would like to delete it because it is a copy. I'm working with the close event for a mail item, but I can't seem to find out how to delete it in that sub, tried many things. Anyone knows how to approach this?
Code I got so far in a module:
Dim itmevt As New CMailItemEvents
Public olMail As Variant
Public olApp As Outlook.Application
Public olNs As NameSpace
Public Fldr As MAPIFolder
Sub TeamcenterWEBAccount()
Dim i As Integer
Dim olMail As Outlook.MailItem
Set olApp = New Outlook.Application
Set olNs = olApp.GetNamespace("MAPI")
Set Fldr = olNs.GetDefaultFolder(olFolderDrafts)
For Each olMail In Fldr.Items
If InStr(olMail.Subject, "New account") <> 0 Then
Set NewItem = olMail.Copy
olMail.Display
Set itmevt.itm = olMail
Exit Sub
End If
Next olMail
End Sub
Code in the CMailItemEvents class module:
Option Explicit
Public WithEvents itm As Outlook.MailItem
Private Sub itm_Close(Cancel As Boolean)
Dim blnSent As Boolean
On Error Resume Next
blnSent = itm.Sent
If blnSent = False Then
itm.DeleteAfterSubmit = True
Else
' do
End Sub
Please, try the next way:
Copy the next adapted code (instead of your code, or in a new standard module):
Option Explicit
Private itmevt As New CMailItemEvents
Public deleteFromDrafts As Boolean, boolContinue As Boolean
Sub TeamcenterWEBAccount()
Dim olMail As Outlook.MailItem, NewItem As Outlook.MailItem, boolDisplay As Boolean
Dim olApp As Outlook.Application, Fldr As MAPIFolder, olNs As NameSpace
Set olApp = New Outlook.Application
Set olNs = olApp.GetNamespace("MAPI")
Set Fldr = olNs.GetDefaultFolder(olFolderDrafts)
For Each olMail In Fldr.Items
If InStr(olMail.Subject, "New account") > 0 Then
On Error Resume Next 'for the case of inline response
Set NewItem = olMail.Copy
If Err.Number = -2147467259 Then
Err.Clear: On Error GoTo 0
olMail.Display: boolDisplay = True
For i = 1 To 1000: DoEvents: Next i 'just wait for the window to be displayed...
Set NewItem = olMail.Copy
End If
On Error GoTo 0
deleteFromDrafts = False: boolContinue = False 'initialize the boolean variables to wait for them in the loop
If Not boolDisplay Then olMail.Display
Set itmevt.itm = olMail
'wait for close event to be triggered...
Do While deleteFromDrafts = False And boolContinue = False
DoEvents
Loop
If deleteFromDrafts Then
If Not olMail Is Nothing Then olMail.Delete 'let only the copy...
End If
Exit Sub
End If
Next olMail
End Sub
Copy the next adapted code to replace the existing one in the used class:
Option Explicit
Public WithEvents itm As Outlook.MailItem
Private Sub itm_Close(Cancel As Boolean)
Dim blnSent As Boolean
On Error GoTo Ending 'for the case of mail sending, when itm looses its reference...
If blnSent = False Then
itm.DeleteAfterSubmit = True
deleteFromDrafts = True
Else
boolContinue = True
End If
Exit Sub
Ending:
boolContinue = True
End Sub
Tested, but not intensively...
Please, send some feedback after testing it in your specific environment.
First of all, iterating over all items in the folder is not really a good idea:
For Each olMail In Fldr.Items
If InStr(olMail.Subject, "New account") <> 0 Then
Instead, let the store provider do the job for you. The Find/FindNext or Restrict methods of the Items class allows getting items that correspond to your conditions, so you could iterate over items needed. Read more about these methods in the following articles:
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
You may try handling the Close event of the Inspector class which is fired when the inspector associated with a Microsoft Outlook item is being closed.
But I think none of them can be helpful. You need to re-design the whole solution by tracking for new items in the folder. And if new items have a custom property which indicates whether to remove the item or not you can do the additional actions. In the item-level event it is impossible to delete the source item.
My changes in module:
Private itmevt As New CMailItemEvents
Public deleteFromDrafts As Boolean, boolContinue As Boolean, boolDisplay As Boolean
Private olMail As Outlook.MailItem, NewItem As Outlook.MailItem
Private olApp As Outlook.Application, olNs As NameSpace, Fldr As MAPIFolder
Sub TeamcenterWEBAccount()
AccountOrInstallation ("Nieuw TC11 VDL ETG Teamcenter WEB account")
End Sub
Sub AccountOrInstallation(ByVal SearchStr As String)
Set olApp = New Outlook.Application
Set olNs = olApp.GetNamespace("MAPI")
Set Fldr = olNs.GetDefaultFolder(olFolderDrafts)
For Each olMail In Fldr.Items
If InStr(olMail.Subject, SearchStr) > 0 Then
On Error Resume Next 'for the case of inline response
Set NewItem = olMail.Copy
If Err.Number = -2147467259 Then
Err.Clear: On Error GoTo 0
olMail.Display: boolDisplay = True
For i = 1 To 1000: DoEvents: Next i 'just wait for the window to be displayed...
Set NewItem = olMail.Copy
End If
On Error GoTo 0
deleteFromDrafts = False: boolContinue = False 'initialize the boolean variables to wait for them in the loop
If Not boolDisplay Then olMail.Display
Set itmevt.itm = olMail
'wait for close event to be triggered...
Do While deleteFromDrafts = False And boolContinue = False
DoEvents
Loop
If deleteFromDrafts Then
If Not olMail Is Nothing Then olMail.Delete 'let only the copy...
End If
Exit Sub
End If
Next olMail
End Sub

Move email after being categorized

I want to move emails, once they are categorized, into a folder with the same name as the category.
What I found so far:
Private WithEvents Explorer As Outlook.Explorer
Private WithEvents Mail As Outlook.MailItem
Private MoveToThisFolder As Outlook.MAPIFolder
Friend Sub Application_Startup()
On Error Resume Next
Set Explorer = Application.ActiveExplorer
End Sub
Private Sub Explorer_SelectionChange()
Dim obj As Object
Dim Sel As Outlook.Selection
Set Mail = Nothing
Set Sel = Explorer.Selection
If Sel.Count > 0 Then
Set obj = Sel(1)
If TypeOf obj Is Outlook.MailItem Then
Set Mail = obj
End If
End If
End Sub
Private Sub Mail_PropertyChange(ByVal Name As String)
Dim Ns As Outlook.NameSpace
Dim Inbox As Outlook.MAPIFolder
Dim Subfolder As Outlook.MAPIFolder
Dim SubfolderName As String
If Name = "Categories" Then
Set Ns = Application.GetNamespace("MAPI")
Set Inbox = Ns.GetDefaultFolder(olFolderInbox)
SubfolderName = Mail.Categories
If Len(SubfolderName) = 0 Then Exit Sub
Set Subfolder = Inbox.Folders(SubfolderName)
If Subfolder.EntryID <> Mail.Parent.EntryID Then
Set MoveToThisFolder = Subfolder
EnableTimer 500, Me
End If
End If
End Sub
Friend Sub TimerEvent()
DisableTimer
If Mail Is Nothing Then Exit Sub
If MoveToThisFolder Is Nothing Then Exit Sub
Mail.Move MoveToThisFolder
Set Mail = Nothing
Set MoveToThisFolder = Nothing
End Sub
I have some problems with respect to Friend Sub TimerEvent () because it gives me
Sub or Function not compiled correctly
At the end i figured out in this way:
Private WithEvents xInboxFld As Outlook.Folder
Private WithEvents xInboxItems As Outlook.Items
Private Sub Application_Startup()
Set xInboxFld = Outlook.Application.Session.GetDefaultFolder(olFolderInbox)
Set xInboxItems = xInboxFld.Items
End Sub
Private Sub xInboxItems_ItemChange(ByVal Item As Object)
Dim xMailItem As Outlook.MailItem
Dim xFlds As Outlook.Folders
Dim xFld As Outlook.Folder
Dim xTargetFld As Outlook.Folder
Dim xFlag As Boolean
On Error Resume Next
If Item.Class = olMail Then
Set xMailItem = Item
xFlag = False
If xMailItem.Categories <> "" Then
Set xFlds = Application.Session.GetDefaultFolder(olFolderInbox).Folders
If xFlds.Count <> 0 Then
For Each xFld In xFlds
If xFld.Name = xMailItem.Categories Then
xFlag = True
End If
Next
End If
If xFlag = False Then
Application.Session.GetDefaultFolder(olFolderInbox).Folders.Add xMailItem.Categories, olFolderInbox
End If
Set xTargetFld = Application.Session.GetDefaultFolder(olFolderInbox).Folders(xMailItem.Categories)
xMailItem.Move xTargetFld
End If
End If
End Sub
Hope it could help!!!
The error is due to missing code for DisableTimer and EnableTimer.
The category has not yet updated when the code is triggered.
EnableTimer delays the move until after the category updates.
Without a delay, there would be an error when attempting to update, due to the item having been moved.
Attribution: http://www.vboffice.net/en/developers/trigger-actions-with-categories/

How can I compare all the titles of all RSS feeds and delete duplicates?

I'm wondering if there is a way to compare ALL TITLES in ALL RSS FEEDS and delete the duplicates.
I read through a lot of RSS Feeds, and it's obvious that a lot of people cross-post to several forums, and then I end up seeing the same RSS Feed multiple times.
I think the script will look something like this, but it doesn't seem to delete dupes.....
Option Explicit
Public Sub DupeRSS()
Dim olNs As Outlook.NameSpace
Dim RSS_Folder As Outlook.MAPIFolder
Set olNs = Application.GetNamespace("MAPI")
Set RSS_Folder = olNs.GetDefaultFolder(olFolderRssFeeds)
'Process Current Folder
Example RSS_Folder
End Sub
Public Sub Example(ByVal ParentFolder As Outlook.MAPIFolder)
Dim itm As Object, itms As Items, dupes As Object, i As Long, k As Variant
Set dupes = CreateObject("Scripting.Dictionary")
Set itms = ParentFolder.Items
For i = itms.Folders.Count To 1 Step -1
Set itm = itms(i)
If TypeOf itm Is PostItem Then
If dupes.Exists(itm.Subject) Then itm.Delete Else dupes(itm.Subject) = 0
Else
Example itm 'Recursive call for Folders
End If
Next i
'Show dictionary items
If dupes.Count > 0 Then
For Each k In dupes
Debug.Print k
Next
End If
Set itm = Nothing: Set itms = Nothing: Set dupes = Nothing
End Sub
Thanks to all!!
Maybe this is what your trying to do, the following code saves/adds all the Items subject line to the collection and then continues to search multiple folders and then deletes if it finds duplicates-
Option Explicit
Public Sub DupeRSS()
Dim olNs As Outlook.NameSpace
Dim RSS_Folder As Outlook.MAPIFolder
Dim DupItem As Object
Set DupItem = CreateObject("Scripting.Dictionary")
Set olNs = Application.GetNamespace("MAPI")
Set RSS_Folder = olNs.GetDefaultFolder(olFolderRssFeeds)
' // Process Current Folder
Example RSS_Folder, DupItem
End Sub
Public Sub Example(ByVal ParentFolder As Outlook.MAPIFolder, _
ByVal DupItem As Object)
Dim Folder As Outlook.MAPIFolder
Dim Item As Object
Dim Items As Items
Dim i As Long
Set Items = ParentFolder.Items
Debug.Print ParentFolder.Name
For i = Items.Count To 1 Step -1
DoEvents
If TypeOf Items(i) Is PostItem Then
Set Item = Items(i)
If DupItem.Exists(Item.Subject) Then
Debug.Print Item.Subject ' Print on Immediate Window
Debug.Print TypeName(Item) ' Print on Immediate Window
Item.Delete
Else
DupItem.Add Item.Subject, 0
Debug.Print DupItem.Count, Item.Subject
End If
End If
Next i
' // Recurse through subfolders
If ParentFolder.Folders.Count > 0 Then
For Each Folder In ParentFolder.Folders
Example Folder, DupItem
Debug.Print Folder.Name
Next
End If
Set Folder = Nothing
Set Item = Nothing
Set Items = Nothing
End Sub
Try the changes bellow
Option Explicit
'Required - VBA Editor -> Tools -> References: Microsfot Outlook XXX Object Library
'Required - VBA Editor -> Tools -> References: Microsfot Scripting Runtime (Dictionary)
Public Sub RemoveRSSduplicates()
Dim olNs As Outlook.Namespace, olApp As Object, rssFolder As Folder, d As Dictionary
Set olApp = GetObject(, "Outlook.Application")
Set olNs = olApp.GetNamespace("MAPI")
Set rssFolder = olNs.GetDefaultFolder(olFolderRssFeeds)
Set d = CreateObject("Scripting.Dictionary")
ProcessOutlookRSSFeeds rssFolder, d
End Sub
Public Sub ProcessOutlookRSSFeeds(ByVal rssFolder As Folder, ByRef d As Dictionary)
Dim fldr As Folder, itm As Object
For Each fldr In rssFolder.Folders
If fldr.Items.Count > 0 Then
For Each itm In fldr.Items
If TypeOf itm Is PostItem Then
If Not d.Exists(itm.Subject) Then d(itm.Subject) = 0 Else itm.Delete
End If
Next
End If
Next
End Sub
Note: avoid variable names that will hide other objects (ex. Dim Items As Items)

Outlook VBA to save sent meeting requests/replies to a specified folder

I found the code below (here) to get outlook VBA to save sent email to a specified folder.
The code works well, however, any time that it runs on a sent meeting request or meeting reply, it errors.
I have been able to identify line 9 as the line where the error occurs:
Set Item.SaveSentMessageFolder = objFolder
My assumption, then, is that the Item.SaveSentMessageFolder code is incompatible with meeting-type objects. However, I am uncertain as to what the equivalent coding would be for meeting-type objects.
Can this code be modified to handle meeting-type objects in the same fashion that message-type objects are handled?
Private Sub Application_ItemSend(ByVal Item As Object, _
Cancel As Boolean)
Dim objNS As NameSpace
Dim objFolder As MAPIFolder
Set objNS = Application.GetNamespace("MAPI")
Set objFolder = objNS.PickFolder
If TypeName(objFolder) <> "Nothing" And _
IsInDefaultStore(objFolder) Then
Set Item.SaveSentMessageFolder = objFolder
End If
Set objFolder = Nothing
Set objNS = Nothing
End Sub
Public Function IsInDefaultStore(objOL As Object) As Boolean
Dim objApp As Outlook.Application
Dim objNS As Outlook.NameSpace
Dim objInbox As Outlook.MAPIFolder
On Error Resume Next
Set objApp = CreateObject("Outlook.Application")
Set objNS = objApp.GetNamespace("MAPI")
Set objInbox = objNS.GetDefaultFolder(olFolderInbox)
Select Case objOL.Class
Case olFolder
If objOL.StoreID = objInbox.StoreID Then
IsInDefaultStore = True
End If
Case olAppointment, olContact, olDistributionList, _
olJournal, olMail, olNote, olPost, olTask
If objOL.Parent.StoreID = objInbox.StoreID Then
IsInDefaultStore = True
End If
Case Else
MsgBox "This function isn't designed to work " & _
"with " & TypeName(objOL) & _
" items and will return False.", _
, "IsInDefaultStore"
End Select
Set objApp = Nothing
Set objNS = Nothing
Set objInbox = Nothing
End Function
At least one other person has found the property to be ineffective for meeting items.
https://msdn.microsoft.com/en-us/vba/outlook-vba/articles/meetingitem-savesentmessagefolder-property-outlook
"Setting or getting this property has no noticeable effect. Do not use this property."
Try monitoring the Sent Items folder.
Place this code in the ThisOutlookSession module.
Private WithEvents sentMailItems As items
Private Sub Application_Startup()
Set sentMailItems = Session.GetDefaultFolder(olFolderSentMail).items
End Sub
Private Sub sentMailItems_ItemAdd(ByVal Item As Object)
Dim objFolder As Folder
If TypeOf Item Is MeetingItem Then
Set objFolder = Session.PickFolder
If TypeName(objFolder) <> "Nothing" Then
Item.Move objFolder
End If
End If
Set objFolder = Nothing
End Sub

Mark as Read loop errors on some mail items inconsistently

I receive 4000+ emails over a weekend and all throughout the week that are filtered into folders via rules. I created a macro to mark all these folders as read. However, on some mail items it errors with runtime error 91 object variable or with block variable not set.
If I skip the errors with On Error Resume Next it loops through everything but just doesn't set a bunch of the mail items as read. I can then rerurn the macro to get most of the remaining ones. If I run the macro 3-4 times it will eventually get them all.
How can I improve this macro to consistently mark ALL the items as read?
Public Function GetInboxFolderID(FolderName As String) As String
Dim nsp As Outlook.Folder
Dim mpfSubFolder As Outlook.Folder
Dim mpfSubFolder2 As Outlook.Folder
Dim flds As Outlook.Folders
Dim flds2 As Outlook.Folders
Set nsp = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
Set flds = nsp.Folders
Set mpfSubFolder = flds.GetFirst
Do While Not mpfSubFolder Is Nothing
If mpfSubFolder.Name = FolderName Then
GetInboxFolderID = mpfSubFolder.EntryID
Exit Function
End If
Set flds2 = mpfSubFolder.Folders
Set mpfSubFolder2 = flds2.GetFirst
Do While Not mpfSubFolder2 Is Nothing
If mpfSubFolder2.Name = FolderName Then
GetInboxFolderID = mpfSubFolder2.EntryID
Exit Function
End If
Set mpfSubFolder2 = flds2.GetNext
Loop
Set mpfSubFolder = flds.GetNext
Loop
End Function
Private Sub processFolder(ByVal oParent As Outlook.MAPIFolder)
Dim oFolder As Outlook.MAPIFolder
Dim oMail As Outlook.MailItem
Dim oFiltered As Outlook.Items
Set oFiltered = oParent.Items.Restrict("[unread] = true")
On Error Resume Next
For Each oMail In oFiltered
oMail.UnRead = False
Next
If (oParent.Folders.Count > 0) Then
For Each oFolder In oParent.Folders
processFolder oFolder
Next
End If
End Sub
Public Sub markNocAsRead()
Dim SubFolder As MAPIFolder
Set SubFolder = Session.GetDefaultFolder(olFolderCalendar).Parent.Folders("NOC Alerts")
'Application.Session.GetFolderFromID (GetInboxFolderID("NOC Alerts"))
Call processFolder(SubFolder)
End Sub
I was able to get this working consistently by taking the advice of Ryan Wildry in a comment above.
I replaced my loop:
Set oFiltered = oParent.Items.Restrict("[unread] = true")
On Error Resume Next
For Each oMail In oFiltered
oMail.UnRead = False
Next
with a loop that iterates from the end to beginning:
Set oFiltered = oParent.Items.Restrict("[unread] = true")
For I = oFiltered.Count To 1 Step -1
Set oMail = oFiltered(I)
oMail.UnRead = False
Next