0
votes

I need to build a tool that will allow the user to select an email from his Outlook so I can then save that email as a .msg file or alternately save just the attachment as a file.

I'm stumbling a little bit over what might be the easiest and the best way to allow searching/filtering of emails. I need to give the user a view that is at least slightly similar to Outlook (for example, folders should be the same order/hierarchy.

Does the Outlook Object Model have some kind of Explorer/Picker/Selection dialog I can call that will return a storeid and an entryid after the user selects an email? Or do I need to roll my own?

I should mention that I already know how to save the email or attachment so my question is only about handling selection and filtering of emails.

FYI, I'm programming this in MS Access 2007 with Outlook 2007. The target machines have either 2007 or 2010 versions of Access and Outlook.

2
You can link Outlook in MS Access.Fionnuala
I've always assumed that Outlook linked tables are more problematic when it comes to compatibility across different workstations, versions of outlook/access, etc. Am I not thinking right?HK1
I am not sure, I have not used them much, which is why I did not post an answer. You can do a lot with Outlook and VBA, but it is a while since I have done it. I generally used automation. Each email does have a unique id.Fionnuala
I just tried it and I see a lot of problems. The linked table doesn't show a StoreID or an EntryID. It looks like the performance is lackluster - one of my primary users has a 17GB .ost file. And it looks like you have to have a linked table for every folder in their store. Not sure how else you'd do it but it seems a little excessive. My quick analysis says that I don't think using outlook linked tables is going to work for this but thanks for the suggestion.HK1
That won't work. I need a general picker tool/screen that can be used anywhere in the Access software. Sometimes an email (or just it's attachment) will need to be attached to a PO, other times to a Quote, maybe to a Contact, etc. Besides, the Access database is already deployed so deploying an Outlook solution across our network would be more difficult at this point.HK1

2 Answers

0
votes

Linking to the Outlook table is fine. The problem is that Outlook doesn't provide a unique ID to each message and if the message is moved from one folder to another, its ID changes. Clearly not designed by someone who understands databases.

A better approach may be to create an Outlook add-in that runs within Outlook, then performs the tasks you need to send the info to Access.

0
votes

I rarely program with Access but I moved some code across from Outlook, hacked it around a bit and it seems to work. This is not a solution but it should show you how to access all the information you need.

I had one problem. Neither Set OutApp = CreateObject("Outlook.Application") nor Set OutApp = New Outlook.Application create a new instance of Outlook if one is already open. So Quit closes Outlook whether or not it was open before the macro started. I suggest you post a new question on this issue; I am sure someone knows how to tell if Outlook is already open and therefore not to quit it.

The folder structure in Outlook is slightly awkward because the top level folders are of type Folders while all sub-folders are of type MAPIFolder. Once you have got past that it is fairly straightforward.

The code below includes function GetListSortedChildren(ByRef Parent As MAPIFolder) As String. This function finds all the children of Parent and returns a string such as "5,2,7,1,3,6,4" which lists the indices for the children in ascending sequence by name. I would use something like this to populates a ListView by expanding nodes as the user required.

I have provided a subroutine CtrlDsplChld() which controls the output to the immediate windows of all the folders in sequence. I believe that should give you enough guidance to get started on accessing the folder hierarchy.

Subroutine DsplChld(ByRef Parent As MAPIFolder, ByVal Level As Long) includes code to find the first message with attachments. This will you tell you how to look through a folder for a particular message.

Finally, CtrlDsplChld() displayes selected properties of the message: Subject, To, HTMLBody and the display names of the attachments.

Hope this helps.

Option Compare Database
Option Explicit
Dim ItemWithMultipleAttachments As Outlook.MailItem
Sub CtrlDsplChld()

  Dim ArrChld() As String
  Dim ListChld As String
  Dim InxAttach As Long
  Dim InxChld As Long
  Dim InxTopLLCrnt As Long
  Dim OutApp As Outlook.Application
  Dim TopLvlList As Folders

  Set ItemWithMultipleAttachments = Nothing

  Set OutApp = CreateObject("Outlook.Application")
  'Set OutApp = New Outlook.Application

  With OutApp

    Set TopLvlList = .GetNamespace("MAPI").Folders

    For InxTopLLCrnt = 1 To TopLvlList.Count
      ' Display top level children and their children
      Call DsplChld(TopLvlList.Item(InxTopLLCrnt), 0)
    Next

    If Not ItemWithMultipleAttachments Is Nothing Then
      With ItemWithMultipleAttachments
        Debug.Print .Subject
        Debug.Print .HTMLBody
        Debug.Print .To
        For InxAttach = 1 To .Attachments.Count
          Debug.Print .Attachments(InxAttach).DisplayName
        Next
      End With
    End If
    .Quit
  End With
  Set OutApp = Nothing

End Sub
Sub DsplChld(ByRef Parent As MAPIFolder, ByVal Level As Long)

  Dim ArrChld() As String
  Dim InxChld As Long
  Dim InxItemCrnt As Long
  Dim ListChld As String

  Debug.Print Space(Level * 2) & Parent.Name

  If ItemWithMultipleAttachments Is Nothing Then
    ' Look down this folder for a mail item with an attachment
    For InxItemCrnt = 1 To Parent.Items.Count
      With Parent.Items(InxItemCrnt)
        If .Class = olMail Then
          If .Attachments.Count > 1 Then
            Set ItemWithMultipleAttachments = Parent.Items(InxItemCrnt)
            Exit For
          End If
        End If
      End With
    Next
  End If

  ListChld = GetListSortedChildren(Parent)
  If ListChld <> "" Then
    ' Parent has children
    ArrChld = Split(ListChld, ",")
    For InxChld = LBound(ArrChld) To UBound(ArrChld)
      Call DsplChld(Parent.Folders(ArrChld(InxChld)), Level + 1)
    Next
  End If

End Sub
Function GetListSortedChildren(ByRef Parent As MAPIFolder) As String

  ' The function returns "" if Parent has no children.
  ' If the folder has children, the functions returns "P,Q,R, ..." where
  ' P, Q, R and so on indices of the children of Parent in ascending
  ' order by name.

  Dim ArrInxFolder() As Long
  'Dim ArrFolder() As MAPIFolder
  Dim InxChldCrnt As Long
  Dim InxName As Long
  Dim ListChld As String

 If Parent.Folders.Count = 0 Then
   ' No children
   GetListSortedChildren = ""
 Else
 'ReDim ArrName(1 To Parent.Folders.Count)
 'For InxChldCrnt = 1 To Parent.Folders.Count
 '  ArrFolder(InxChldCrnt) = Parent.Folders(InxChldCrnt)
 'Next
 Call SimpleSortMAPIFolders(Parent, ArrInxFolder)
   ListChld = CStr(ArrInxFolder(1))
   For InxChldCrnt = 2 To Parent.Folders.Count
     ListChld = ListChld & "," & CStr(ArrInxFolder(InxChldCrnt))
   Next
   GetListSortedChildren = ListChld
 End If
End Function
Sub SimpleSortMAPIFolders(ArrFolder As MAPIFolder, _
                                        ByRef InxArray() As Long)

  ' On exit InxArray contains the indices into ArrFolder sequenced by
  ' ascending name.  The sort is performed by repeated passes of the list
  ' of indices that swap adjacent entries if the higher come first.
  ' Not an efficient sort but adequate for short lists.

  Dim InxIACrnt As Long
  Dim InxIALast As Long
  Dim NoSwap As Boolean
  Dim TempInt As Long

  ReDim InxArray(1 To ArrFolder.Folders.Count)  ' One entry per sub folder
  ' Fill array with indices
  For InxIACrnt = 1 To UBound(InxArray)
    InxArray(InxIACrnt) = InxIACrnt
  Next

  If ArrFolder.Folders.Count = 1 Then
    ' One entry list already sorted
    Exit Sub
  End If

  ' Each repeat of the loop moves the folder with the highest name
  ' to the end of the list.  Each repeat checks one less entry.
  ' Each repeats partially sorts the leading entries and may result
  ' in the list being sorted before all loops have been performed.
  For InxIALast = UBound(InxArray) To 1 Step -1
    NoSwap = True
    For InxIACrnt = 1 To InxIALast - 1
      If ArrFolder.Folders(InxArray(InxIACrnt)).Name > _
         ArrFolder.Folders(InxArray(InxIACrnt + 1)).Name Then
        NoSwap = False
        ' Move higher entry one slot towards the end
        TempInt = InxArray(InxIACrnt)
        InxArray(InxIACrnt) = InxArray(InxIACrnt + 1)
        InxArray(InxIACrnt + 1) = TempInt
      End If
    Next
    If NoSwap Then
      Exit For
    End If
  Next

End Sub