Adding multiple attachments from a folder

Published 31 July 08 02:55 PM

Rob Cooper recently made a post, Adding Attachments from a Folder, which shows how to add a single attachment file per row.  But what if you wanted to add more than one file?  One (highly contrived) scenario would be to add all of the files in a folder to one row, and do so recursively if requested, similar to Rob's example.

What follows is some VBA code to do this, which borrows a bit from Rob's post and a bit from one of my earlier posts.  However, I should point out this (again, highly contrived scenario) is meant only as an example and not something I would recommend doing, because if you are adding more than just a few files you can (ok, WILL) bloat your database very quickly, and in doing so can negatively impact performance and potentially hit the two gigabyte file size limit very quickly (see Access 2007 Specifications for details on database file size and object limitations).

First, to use the following sample code you will need to do some setup.  Create a new table, add the following fields, and save it as Table1:

Table1

Field Name Data Type
ID Autonumber (Primary Key)
FolderPath Text
Files Attachment

image

Next, open the VBE window (ALT+F11) and insert a new module (Insert -> Module), and paste in the following code:

' -------------------------------------------------------------------------
' Procedure : StoreFilesInTable
' Purpose   : Adds all files matching the specified file mask from the
'           : specified folder to an attachment field.
'           : Each row in the represents all files stored from the folder.
' Arguments : strFolder - The path to the folder stored in the attachment field.
'           : strTable  - The name of the table containing the attachment field.
'           : strPathField - The name of the field for the archived folder.
'           : strAttachmentField - The name of the attachment field. [Files]
'           : strPattern - File mask. [*.*]
'           : blnIncludeSubfolders - Recurse into subfolders. [False]
'           : db1 - The database to operate on.  [CurrentDb]
' Comments  : The db1 param is included so this can be used to store files in
'           : a separate database, since using the attachment field this way
'           : can quickly push a database beyond the 2gb file size limit.
' -------------------------------------------------------------------------
Public Function StoreFilesInTable( _
                ByVal strFolder As String, _
                ByVal strTable As String, _
                ByVal strPathField As String, _
                Optional ByVal strAttachmentField As String = "Files", _
                Optional ByVal strPattern As String = "*.*", _
                Optional ByVal blnIncludeSubfolders As Boolean = False, _
                Optional ByRef db1 As DAO.Database)

    Const CALLER = "StoreFilesInTable"
    On Error GoTo StoreFilesInTable_ErrorHandler

    Dim strFilePath As String
    Dim rstParent   As DAO.Recordset2
    Dim rstChild    As DAO.Recordset2
    Dim fldAttach   As DAO.Field2

    ' These objects require a reference to the "Microsoft Scripting Runtime"
    ' but are defined as "Object" instead to use late binding and avoid that.
    ' If you've added the reference, remove the "Object" and uncomment the
    ' following lines to get the intellisense autocomplete for these objects.
    Dim objFso       As Object ' Scripting.FileSystemObject
    Dim objFolder    As Object ' Scripting.Folder
    Dim objSubFolder As Object ' Scripting.Folder
    Dim objFile      As Object ' Scripting.File

    ' If the user did not specify a database, use the current one.
    If db1 Is Nothing Then Set db1 = Application.CurrentDb

    ' Instantiate the FileSystemObject.
    Set objFso = CreateObject("Scripting.FileSystemObject")

    ' Make sure the folder path always ends with a "\".
    If (Right(strFolder, 1) <> "\") Then strFolder = strFolder & "\"

    ' Make sure the folder exists.
    If Not objFso.FolderExists(strFolder) Then
        MsgBox "Folder does not exist: " & strFolder, _
        vbExclamation, CALLER
        Exit Function
    End If

    ' It exists, so get the folder object.
    Set objFolder = objFso.GetFolder(strFolder)

    ' Open the table containing the attachment field
    Set rstParent = db1.OpenRecordset(strTable)

    rstParent.AddNew
    rstParent.Fields(strPathField).Value = objFolder.Path

    ' Get the first file in this directory.
    strFilePath = Dir(strFolder & strPattern)

    ' Store each file that meets the pattern
    While (Len(strFilePath) > 0)
        Set rstChild = rstParent.Fields(strAttachmentField).Value
        rstChild.AddNew
        Set fldAttach = rstChild.Fields("FileData")
        fldAttach.LoadFromFile strFolder & strFilePath
        rstChild.Update
        rstChild.Close
        strFilePath = Dir() ' Get the next file
    Wend

    ' Commit the new row with the attachments field populated
    ' with all of the files from the current folder.
    rstParent.Update

    ' Recurse into subfolders if requested.
    If (blnIncludeSubfolders) Then
        For Each objSubFolder In objFolder.SubFolders
            StoreFilesInTable objSubFolder.Path, strTable, _
                              strPathField, strAttachmentField, _
                              strPattern, blnIncludeSubfolders, db1
        Next
    End If

Cleanup:
    rstParent.Close
    Set rstParent = Nothing

    Exit Function
StoreFilesInTable_ErrorHandler:
    Debug.Print "Error # " & Err.Number & " in " & CALLER & " : " & Err.Description
    MsgBox Err.Description, vbCritical, "Error # " & Err.Number & " in " & CALLER
    GoTo Cleanup
End Function 'StoreFilesInTable

 

Here is a short routine to help you test the above code.  You will need to change the "<YourUserName>" to your login name, or just change the whole path in the strRootFolder string constant to the folder you want to store in the table.  Note that I've set the blnIncludeSubfolders parameter to False to keep you from inadvertently bloating your database, but you can set it to True if you want to include all of the subfolders, too.

Sub TestStoreFilesInTable()
    Const strRootFolder As String = "C:\Users\<YourUserName>\Pictures\"
    StoreFilesInTable strRootFolder, "Table1", "FolderPath", "Files", "*.jpg", False
    MsgBox "Done adding files from: " & vbCrLf & strRootFolder & "*.jpg", _
        VbMsgBoxStyle.vbInformation, "TestStoreFilesInTable"
End Sub

Comments

# Vladimir Cvajniga said on August 1, 2008 8:36 AM:

Off-topic: Where can I report A2007 bugs?

# Vladimir Cvajniga said on August 1, 2008 8:54 AM:

How do I export A2007 report to PDF? I desperately NEED to apply filter on the report data. It's very easy with PDFCreator but I can't do it in A2007. I'm stuck!

# Vladimir Cvajniga said on August 1, 2008 9:03 AM:

P.S. I can't do it in A2007 without PDFCreator.

P.P.S. How do I export macro to VBA in A2007?

# clintc said on August 1, 2008 11:26 AM:

Vladimir,

Feel free to send me bug reports. Most of the bugs we take in Access 2007 codebase are issues reported by customers through our support organization. The bar for fixes is pretty high as we don't want to introduce other regressions.

Export a report to pdf should be straight forward. Download and install the addin. Open a report in browse mode or open the report in print preview with a filter. If you use browse mode use the right click filters to filter it down. Then you can use External Data | Export | PDF/XPF. The integration should be just like any other export formats.

WRT - save macro as VBA... Open the form/report in design view. Use the command Database Tools | Macros | Convert Form's Macro to Visual Basic.

# Markus said on August 1, 2008 6:10 PM:

Many thanks for this Posting. It gifs me a good solution for one of my problems. Thanks and many greagings.

# Vladimir Cvajniga said on August 2, 2008 3:58 AM:

Clint C.

I'll report some bugs & issues soon.

PDF: I'd appreciate if there was an option to set filter during DoCmd.OutputTo and DoCmd.SendObject. I have a procedure which automatically creates more than 30 reports (accountings - annular analysis & report) without user's response. I need to apply filter for some of the reports and I need to do it in VBA to automate the process. So, if I want to use Office 2007's PDF add-in I must do some more coding in reports to apply filter through a global variable. I can't use report's OpenArgs to apply the filter.

PDF printer would do the trick. Office 2007's PDF add-in installs a virtual printer "Microsoft XPS Document Writer". I'd appreciate "Microsoft PDF Document Writer" as well.

# Albert D. Kallal said on August 3, 2008 2:15 PM:

>I must do some more coding in reports to apply filter through a global variable

Don’t use global here (they are not needed and it is a VERY poor coding practice).

Open the report in preview mode + where clause, and then send it to pdf.

Here a simple code snip

  Dim strR       As String

  strR = "contacts"

  DoCmd.OpenReport strR, acViewPreview, , "ID = " & Me!ID

  Reports(strR).visible = False

  DoCmd.OutputTo acOutputReport, strR, acFormatPDF, "c:\t.pdf"

  DoCmd.Close acReport, strR

You of course could/would wrap the above code in a nice sub that you can call over and over...

Albert D. Kallal

Edmonton, Alberta Canada

# Vladimir Cvajniga said on August 3, 2008 4:50 PM:

Albert D. Kallal said on August 3, 2008 2:15 PM:

Thanks a million for the trick! Couldn't find it in A2007 help examples.

Not sure of using this scenario since the screen is blinking due to acViewPreview. I'd like to let the Echo True so that users can see some action. But I really hate blinking screens... so do many users. I'll keep trying to adapt your code snipet for a non-blinking function.

# Albert D. Kallal said on August 3, 2008 9:32 PM:

If you turn off the application echo before you launch the report, you not see any echo from the report.

  strR = "contacts"

  Application.Echo False

  DoCmd.OpenReport strR, acViewPreview, , "ID = " & Me!ID

  Reports(strR).visible = False

  DoCmd.OutputTo acOutputReport, strR, acFormatPDF, "c:\t.pdf"

  DoCmd.Close acReport, strR

  Application.Echo True

The flicker you see is likey the dialog box from use of outputTo.

I kind of like that dialog since it shows somthing is going on. So, give the echo idea you mentioned a try.

# Albert D. Kallal said on August 3, 2008 9:34 PM:

by the way, you *can* surpress the dialog box. there is some code at the access web, not sure if it works with 07

New Comments to this post are disabled

About James K. Rivera

I’m a Software Design Engineer in Test II (a.k.a. SDET II) on the Access team. I’ve been at Microsoft for over 11 years, and I've worked on many different teams, mostly in Office. I started working on Schedule+ in the Exchange server team, then I moved to Outlook for 98/2000, Office Programmability for Office XP, Excel for Office 2003, and finally the Access team for Office 2007 where I still work today. For Access 2007, I worked on the new complex data types (fields that allow multiple values) such as the new Attachment data type, and I owned the testing of DAO for the privatized version of JET called the Access database engine. More recently I worked on the Package Solution Wizard in the new Access Developer Extensions. I’m passionate about programming--particularly when it comes to programming in Office applications such as Access 2007, or Excel. I wrote the Excel 2003 XML Tools Add-in Version 1.1 (which can be found at "http://msdn2.microsoft.com/en-us/library/Aa203739(office.11).aspx"). When I'm not programming in Access, I enjoy spending time with friends, working in the yard, watching movies, traveling, playing billiards, ultimate Frisbee, softball, golf, guitar, and probably way too much Halo on my Xbox 360.
Page view tracker