After my previous post
I decided to continue development and hence today I present to you an approach to searching for appointments. The beauty here is once you implement this type of framework you can then easily:
- Find Outlook Appointment and Save them to disk
- Find Outlook Appointment and Display them to the user for their interaction
- Find Outlook Appointment and Update them and then save the changes
- Find Outlook Appointment and Delete them.
- You could retrieve the items to display in a form for user interaction
- …
Furthermore, such a Framework can be applied to just about any Outlook Items:
- Contacts
- Appointments
- …
In the example code below, I am saving the filtered items to disk.
So, in this code I provide conditional compilation to enable both Early and Late Binding, so the choice is yours! Of course, if we are to use Late Binding, we need to set up some Enums to facilitate coding and then there are a couple general helper functions as well.
Helper Functions
'---------------------------------------------------------------------------------------
' Procedure : SanitizeFileNameString
' Author : Daniel Pineault, CARDA Consultants Inc.
' Website : http://www.cardaconsultants.com
' Purpose : Sanitize the supplied string to follow Windows file naming conventions
' ie: avoid illegal characters
' Copyright : The following is release as Attribution-ShareAlike 4.0 International
' (CC BY-SA 4.0) - https://creativecommons.org/licenses/by-sa/4.0/
' Req'd Refs: Late Binding -> none required
' References: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' sInput : filename to validate/sanitize
'
' Usage:
' ~~~~~~
' SanitizeFileNameString("some file > ?")
' Returns -> some file _ _
'
' Revision History:
' Rev Date(yyyy-mm-dd) Description
' **************************************************************************************
' 1 2001-11-09
' 2 2024-01-09 Updated header and added References link
'---------------------------------------------------------------------------------------
Public Function SanitizeFileNameString(sInput As String) As String
On Error GoTo Error_Handler
Dim sOutput As String
Const sReplacementChr As String = "_" 'Could be a function Input Arg.
sOutput = sInput
' Windows naming convention
sOutput = Replace(sOutput, "<", sReplacementChr)
sOutput = Replace(sOutput, ">", sReplacementChr)
sOutput = Replace(sOutput, ":", sReplacementChr)
sOutput = Replace(sOutput, """", sReplacementChr)
sOutput = Replace(sOutput, "/", sReplacementChr)
sOutput = Replace(sOutput, "\", sReplacementChr)
sOutput = Replace(sOutput, "|", sReplacementChr)
sOutput = Replace(sOutput, "?", sReplacementChr)
sOutput = Replace(sOutput, "*", sReplacementChr)
' Personal convention
'sOutput = Replace(sOutput, "'", sReplacementChr)
'sOutput = Replace(sOutput, " ", sReplacementChr)
sOutput = Replace(sOutput, ".", sReplacementChr)
SanitizeFileNameString = sOutput
Error_Handler_Exit:
On Error Resume Next
Exit Function
Error_Handler:
MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
"Error Source: SanitizeFileNameString" & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occurred!"
Resume Error_Handler_Exit
End Function
Public Function GetSaveAsTypeExtension(ByVal lSaveAsFormat As OlSaveAsType, _
Optional bIncludePeriod As Boolean = False) As String
Dim sOutput As String
Select Case lSaveAsFormat
Case 0
sOutput = "txt"
Case 1
sOutput = "rtf"
Case 2
sOutput = "oft"
Case 3
sOutput = "msg"
Case 4
sOutput = "doc"
Case 5
sOutput = "html"
Case 6
sOutput = "vcf"
Case 7
sOutput = "vcs"
Case 8
sOutput = "ics"
Case 9
sOutput = "msg"
Case 10
sOutput = "mht"
End Select
If bIncludePeriod Then sOutput = "." & sOutput
GetSaveAsTypeExtension = sOutput
End Function
Enum
#If Outlook_EarlyBind = False Then
Enum OlDefaultFolders
'https://learn.microsoft.com/en-us/office/vba/api/outlook.oldefaultfolders
' *** There are more elements to this Enum, see link above for complete listing
olFolderUnspecified = 0
olFolderDeletedItems = 3
olFolderOutbox = 4
olFolderSentMail = 5
olFolderInbox = 6
olFolderDrafts = 16
olFolderJunk = 23
olFolderCalendar = 9
olFolderConflicts = 19
olFolderContacts = 10
olFolderJournal = 11
olFolderLocalFailures = 21
olFolderManagedEmail = 29
olFolderNotes = 12
olFolderServerFailures = 22
olFolderSuggestedContacts = 30
olFolderSyncIssues = 20
olFolderTasks = 13
olFolderToDo = 28
olPublicFoldersAllPublicFolders = 18
olFolderRssFeeds = 25
End Enum
Enum OlObjectClass
'https://learn.microsoft.com/en-us/office/vba/api/outlook.olobjectclass
olObjectClassUnspecified = 0
olContact = 40
olMail = 43
olAppointment = 26
olJournal = 42
olNote = 44
olTask = 48
End Enum
Enum OlSaveAsType
'https://learn.microsoft.com/en-us/office/vba/api/Outlook.OlSaveAsType
olDoc = 4
olHTML = 5
olICal = 8
olMHTML = 10 'Not in the contact saveas method?
olMSG = 3
olMSGUnicode = 9
olRTF = 1
olTemplate = 2
olTXT = 0
olVCal = 7
olVCard = 6
End Enum
#End If
Core Code
#Const Outlook_EarlyBind = 0 '0 => Late Binding
'-1 => Early Binding which requires a Ref to 'Microsoft Outlook XX.X Object Library'
'Searches the specified folder ,and, optionally, process subfolders as well
Sub Outlook_FindAndSave_Advanced(Optional sFilter As String, _
Optional bProcessSubFolders As Boolean = False, _
Optional sSaveFolder As String, _
Optional lFolder As OlDefaultFolders, _
Optional sSubFolder As String, _
Optional lSaveAsFormat As OlSaveAsType = olVCard)
#If Outlook_EarlyBind Then
Dim oOutlook As Outlook.Application
Dim oNS As Outlook.NameSpace
Dim oFolder As Outlook.MAPIFolder
#Else
Dim oOutlook As Object
Dim oNS As Object
Dim oFolder As Object
#End If
Dim bOutlookOpened As Boolean
Dim sExtension As String
Dim aFolders() As String
Dim i As Long
On Error Resume Next
Set oOutlook = GetObject(, "Outlook.Application") 'Bind to existing instance of Outlook
If Err.Number <> 0 Then 'Could not get instance of Outlook, so create a new one
Err.Clear
#If Outlook_EarlyBind Then
Set oOutlook = New Outlook.Application
#Else
Set oOutlook = CreateObject("Outlook.Application")
#End If
bOutlookOpened = False 'Outlook was not already running, we had to start it
Else
bOutlookOpened = True 'Outlook was already running
End If
On Error GoTo Error_Handler
DoEvents
Set oNS = oOutlook.GetNamespace("MAPI")
'Bind to the root folder to process messages in
If lFolder = olFolderUnspecified Then
lFolder = olFolderInbox 'Default it to the Inbox
End If
Set oFolder = oNS.GetDefaultFolder(lFolder)
If sSubFolder <> "" Then
'Break the subfolders into individual subfolders
aFolders = Split(sSubFolder, "\")
'Iterate over each subfolder to set the initial starting point
For i = 0 To UBound(aFolders)
If Len(Trim(aFolders(i) & "")) > 0 Then
Set oFolder = oFolder.Folders(aFolders(i))
End If
Next i
End If
sExtension = GetSaveAsTypeExtension(lSaveAsFormat, True)
'Begin the e-mail message search/filter and save process
Call Outlook_FindAndSaveInFolder(oFolder, sFilter, bProcessSubFolders, sSaveFolder, lSaveAsFormat, sExtension)
If bOutlookOpened = False Then 'Since we started Outlook, we should close it now that we're done
oOutlook.Quit 'There seems to be a delay in this action taking place, but does eventually take place
End If
Error_Handler_Exit:
On Error Resume Next
Set oFolder = Nothing
Set oNS = Nothing
Set oOutlook = Nothing
Exit Sub
Error_Handler:
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: Outlook_FindAndSave_Advanced" & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
Resume Error_Handler_Exit
End Sub
#If Outlook_EarlyBind Then
Sub Outlook_FindAndSaveInFolder(ByVal oFolder As Outlook.MAPIFolder, _
sFilter As String, _
Optional bProcessSubFolders As Boolean = False, _
Optional sSaveFolder As String, _
Optional lSaveAsFormat As OlSaveAsType = olVCard, _
Optional sExtension As String = "vcf")
#Else
Sub Outlook_FindAndSaveInFolder(ByVal oFolder As Object, _
sFilter As String, _
Optional bProcessSubFolders As Boolean = False, _
Optional sSaveFolder As String, _
Optional lSaveAsFormat As OlSaveAsType = olVCard, _
Optional sExtension As String = "vcf")
#End If
On Error GoTo Error_Handler
#If Outlook_EarlyBind Then
Dim oFilterItems As Outlook.Items
Dim oSubFldr As Outlook.MAPIFolder
#Else
Dim oFilterItems As Object
Dim oSubFldr As Object
#End If
Dim oFilterItem As Object
Dim sFileName As String
'https://learn.microsoft.com/en-us/office/vba/api/outlook.olobjectclass
'Const olAppointment = 26
'Apply our search criteria ir applicable
If sFilter = "" Then
Set oFilterItems = oFolder.Items
Else
Set oFilterItems = oFolder.Items.Restrict(sFilter)
End If
'Apply a sort, not necessary for our purposes but left if using for general search purposes
'oFilterItems.Sort "[SentOn]", True 'Sort the results, Descending order (True)
'Set the Folder to save the files to
If sSaveFolder = "" Then
sSaveFolder = Application.CurrentProject.Path & "\emails\"
End If
If Right(sSaveFolder, 1) <> "\" Then sSaveFolder = sSaveFolder & "\"
'Debug.Print "Saving " & oFilterItems.Count & " messages to disk."
'Iterate over the matching items and save them to disk
For Each oFilterItem In oFilterItems
If oFilterItem.Class = olAppointment Then 'olContact
'Build a File Name to save the msg under
sFileName = sSaveFolder
'sFileName = sFileName & oFilterItem.EntryId
sFileName = sFileName & Format(oFilterItem.Start, "yyyymmddhhnnss")
sFileName = sFileName & "_"
sFileName = sFileName & SanitizeFileNameString(oFilterItem.Subject) & sExtension
'https://learn.microsoft.com/en-us/office/vba/api/outlook.contactitem.saveas
oFilterItem.SaveAs sFileName, lSaveAsFormat
End If
DoEvents
Next
'Process any SubFolders
If bProcessSubFolders Then
If oFolder.Folders.Count > 0 Then
For Each oSubFldr In oFolder.Folders
'Debug.Print "Processing Folder: " & oSubFldr.Name
Call Outlook_FindAndSaveInFolder(oSubFldr, sFilter, bProcessSubFolders)
DoEvents
Next
End If
End If
Error_Handler_Exit:
On Error Resume Next
Set oSubFldr = Nothing
Set oFilterItem = Nothing
Set oFilterItems = Nothing
Set oFolder = Nothing
Exit Sub
Error_Handler:
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: Outlook_FindAndSaveInFolder" & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
Resume Error_Handler_Exit
End Sub
Usage
To use the above we could do something like:
Dim sFilter As String sFilter = "[Organizer]='Jane Doe'" Call Outlook_FindAndSave_Advanced(sFilter, , , olFolderCalendar, , olTXT)
which will find all the meetings where ‘Jane Doe’ is the Organizer and export them as txt files.
Dim sFilter As String sFilter = "[Start]='1/16/2024 1:30 PM'" Call Outlook_FindAndSave_Advanced(sFilter, , , olFolderCalendar, , olVCal)
which will export any appointments starting at 1:30 PM on January 16th, 2024 as vcal format (vcs files).
And you can combine filters as you see fit by using the standard And & OR
Dim sFilter As String sFilter = "[Organizer]='Jane Doe'" sFilter = sFilter & " AND " sFilter = "[Start]>'1/1/2023 0:00 AM'" Call Outlook_FindAndSave_Advanced(sFilter, , , olFolderCalendar, , olVCal)
which will export all the meetings organized by ‘Jane Doe’ after ‘1/1/2023 0:00 AM’.
Changing the Default Action
The current code uses the line
oFilterItem.SaveAs sFileName, lSaveAsFormat
to save the item(s) to disk. So you need only override this line to change the action to be taken with matching item. For instance, if you wanted to open the item(s) you could replace that line with
oFilterItem.Display
and now any matching calendar items will be opened instead of saved to disk.