About Outlook Macros
Writing VBA code in Microsoft Outlook is no different from working in your other Microsoft Office programs. Outlook 2000 and 2002 have the familiar Visual Basic Editor with all the same tools you are used to. Outlook 97 does not have a Visual Basic Editor, instead using VBScript attached to custom forms for its macros. But this doesn't mean that this project isn't suitable for Outlook 97 users. You can program Outlook 97 using VBA Automation from one of the other Office programs. Only a few modifications to the code are required and a full explanation of how to do this is given at the end of this tutorial. Outlook 97 users might want to read through this section first.
Unlike other Microsoft Office programs, Outlook can support only one VBA Project at a time. This makes sense really, as you open only one instance of Outlook at a time unlike for example Word or Excel in which each open document or workbook can host its own VBA Project.
There is no simple way to distribute Outlook VBA code to users other than exporting VBA modules and relying on the user to import them into the VBA Project in their own copy of Outlook. Outlook VBA code can be distributed using COM Add-ins but this is beyond the scope of this tutorial. But this need not be a problem if you are creating Outlook macros for your own use, or if you are able to import code modules into other people's copies of the program (or can trust them to do it themselves!).
A Macro to Collect Email Attachments
Start Outlook 2000 or 2002 and open the Visual Basic Editor (Tools > Macro > Visual Basic Editor or Keys: Alt+F11). The Project Explorer window displays the current (and only) project. Add a new code module by choosing Insert > Module and give it a suitable name by entering it into the Name section of the Properties window. I have chosen to call my module GetEmailAttachments.
The module's code window should open automatically. If it doesn't, open it by double-clicking the module name in the Project Explorer window. You're now ready to build the code for the macro...
Name the Macro, Add an Error Handler and Declare the Variables
Enter a suitable name for your macro. I've chosen to call it GetAttachments
Sub GetAttachments() End Sub
It's good practice to add an error handling routine to all your macros, just in case something goes wrong, and the statement that tells the macro where to find it should always come at the top of your code:
On Error GoTo GetAttachments_err
This line tells the macro to jump to a label (like a bookmark in your code) called GetAttachments_err that it will find at the end of the procedure (we'll create it later). Next, declare the variables we are going to use in the macro:
Dim ns As NameSpace Dim Inbox As MAPIFolder Dim Item As Object Dim Atmt As Attachment Dim FileName As String Dim i As Integer
If you haven't programmed Outlook before some terms here will be unfamiliar. I'll explain what these variables are going to be used for...
- The NameSpace is the object that gives you access to all Outlook's folders. In Outlook there is only one and it is called "MAPI" which is an acronym for Messaging Application Programming Interface.
- We are going to be referring to a mail folder (a MAPIFolder object) which we have given the name Inbox but the macro won't know which actual folder that is until we tell it by giving the variable a value in the next step.
- We will be using Atmt to refer to the attachment objects we are looking for.
- The FileName variable is a text string that will be used to create a name and save path for each attachment as it is saved.
- The integer variable i will be used as a counter to log the progress of the macro.
Set the Values of Variables
Some of the variables need to have their values set at the start of the macro:
Set ns = GetNamespace("MAPI") Set Inbox = ns.GetDefaultFolder(olFolderInbox) i = 0
Note that when you are setting the value of the Inbox variable the Visual Basic Editor provides a list of all the possible default folders. This gives an indication of the possibilities when programming Outlook. Here, you need to choose olFolderInbox from the list.
Search the Folder for Attachments
First of all, we need an If Statement to check that there are messages in the Inbox folder, and abandon the search if there are none. The first line of the If Statement counts the number of items in the folder:
If Inbox.Items.Count = 0 Then MsgBox "There are no messages in the Inbox.", vbInformation, _ "Nothing Found" Exit Sub End If
If there are no message in the Inbox folder the macro displays a message to the user and then exits (i.e. terminates). If, however, there are messages in the Inbox folder the macro proceeds to examine each for attachments. This is done using a pair of nested loops (i.e. one loop running inside the other:
For Each Item In Inbox.Items For Each Atmt In Item.Attachments FileName = "C:\Email Attachments\" & Atmt.FileName Atmt.SaveAsFile FileName i = i + 1 Next Atmt Next Item
The outer loop (For Each Item... Next Item) looks at each Item in the Inbox folder in turn. Although the Item variable was declared as an Object it refers to mail messages, which are by definition the only objects that the Inbox folder contains because it is a mail folder. The outer loop runs for as many times as there are mail messages in the folder.
Each time the outer loop encounters a mail message the inner loop (For Each Atmt... Next Atmt) runs for as many times as there are files attached to the mail message. If there are no attached files the macro continues with to the outer loop.
If the inner loop finds an attached file it constructs a text string (the FileName variable) representing the save path of the file. The save path consists of the full path to the folder in which the attachments are to be saved, plus the actual filename of the attachment.
Note that I have "hard coded" the folder path. I could have added some code to allow the user to choose a folder to save the attachments in (but I'm trying to keep things simple!). If you hard code a folder path like this you must make sure that the folder exists before you run the macro. The code will not create it for you.
The inner lop then saves the attachment to the chosen folder and increments the counter variable (i) by one. This counter will be used later to notify the user how many attachments were found.
Display a Summary Message
After the loops have examined all the mail messages, the only job that remains is to notify the user of the results. I have been counting attachments using the variable i. The value it now holds represents the number of attachments that were found and saved. I am using an If Statement to display a message to the user:
If i > 0 Then MsgBox "I found " & i & " attached files." _ & vbCrLf & "I have saved them into the C:\Email Attachments folder." _ & vbCrLf & vbCrLf & "Have a nice day.", vbInformation, "Finished!" Else MsgBox "I didn't find any attached files in your mail.", vbInformation, _ "Finished!" End If
If i is greater than zero then attachments were found and saved so the message tells the user how many (again using the value of i) and where they were saved to.
If i is not greater than zero (i.e. it must be zero) then the user is notified that no attachments were found.
Clear the Memory
The main work of the macro has been done. There a couple of housekeeping tasks to perform. The first task is to clear the computer's memory by setting the value of any object variables (i.e. variable whose values are assigned using the keyword Set) to "Nothing":
GetAttachments_exit: Set Atmt = Nothing Set Item = Nothing Set ns = Nothing Exit Sub
The first line of this code is a label. It will be used by the error handling procedure (coming next). In VBA labels are always followed by a colon and are not regarded as executable code, merely a kind of "bookmark".
After each of the object variables are set to "Nothing" the line Exit Sub tells the macro that if it reaches this point it can finish. The reason it is here is that this is not the last line of code in the procedure. Next comes the Error Handler and it is necessary to terminate the macro before the Error Handler (unless, of course, there is an error!).
I have included a basic "all purpose" error handler here. Remember that at the beginning of the macro code I put the statement: On Error GoTo GetAttachments_err. That statement gave the instruction that, in the event of an error, the macro should jump down to the label GetAttachments_err and continue from there. Here is what is finds:
GetAttachments_err: MsgBox "An unexpected error has occurred." _ & vbCrLf & "Please note and report the following information." _ & vbCrLf & "Macro Name: GetAttachments" _ & vbCrLf & "Error Number: " & Err.Number _ & vbCrLf & "Error Description: " & Err.Description _ , vbCritical, "Error!" Resume GetAttachments_exit
In this case my error handler consists simply of a message notifying the user of the details of the error. This is followed by the statement: Resume GetAttachments_exit which tells the macro to jump up to the label GetAttachments_exit and continue from there. This makes sure that any loose ends are tied up before the macro terminates.
The reason for including an error handler is that this gives the developer the opportunity to deals with errors in an appropriate way. If, for example, I knew that a specific error was likely to occur if the user had forgotten to do something important before running the macro, I could include code in my error handler to deal with it in an appropriate way.
If you don't include an error handler, the user sees the standard VBA error message:
This message is intended for the eyes of the VBA developer. The average user has no idea what it means and will probably press the Debug button because that is the highlighted one and to most users that means "Press Me!". Of course that is exactly what they should not do because it takes them into the Visual Basic Editor window with their macro in break mode. The only safe thing for the user to do is press the End button, which will terminate the macro - but, of course, they don't know that!
But with an error handler in place you can take charge and display your own message instead:
The error handler code safely shuts down the macro without panicking the user and, all being well, without causing any harm!
At last we can end the procedure with the line:
Test and Save the Macro
Before running the macro you should check it for obvious errors and save the code module. Choose to Debug > Compile Projectname to compile the code and check for any coding errors that were not apparent when you typed the code. Then save File > Save VbaProject.OTM to save the changes you have made to Outlook's code:
Running the Macro
Macros in Outlook can be run in the same way as you would run a macro in any other Microsoft Office program. From the Outlook main window choose Tools > Macro > Macros to display the Macros dialog, select your macro, and click Run...
Alternatively you can create a custom toolbar button or menu item and assign the macro to it.
When the macro finishes it displays a message detailing how many attachments were found and where the were saved:
Or, if no attachments were found:
If the Inbox folder is empty this message will be displayed:
When this macro saves attached files, any files already in the target folder with the same filenames as the new files will be overwritten without warning. See the note in Additional Refinements below on how to include a datestamp in the filename.
Remember to create the target folder before running the macro. If the save path is incorrect, misspelled or doesn't exist the macro will return an error (although no harm will be done).
I wanted to keep this tutorial as simple as possible, so it only makes use of a fraction of the VBA programming power that can be used in Outlook. Future tutorials will explore other areas, but here are some refinements that can be added to the macro to make it more powerful...
Save Only Specific File Types
You may want to save only files of a specific type, such as Excel workbooks or Word documents. A simple If Statement can check the file extension and only save files that have, for example, an extension ending in xls (for Excel workbooks). The additions are marked in red:
For Each Item In Inbox.Items For Each Atmt In Item.Attachments If Right(Atmt.FileName, 3) = "xls" Then FileName = "C:\Email Attachments\" & Atmt.FileName Atmt.SaveAsFile FileName i = i + 1 End If Next Atmt Next Item
Look in a Named Subfolder
If you are in the habit of filing your mail messages into subfolders, perhaps doing this automatically by setting up a rule for incoming mail, you might want to scan a specific subfolder of the Inbox rather than the Inbox folder itself. This example will cause the macro to search in a folder called Sales Reports which is located inside the main Inbox folder. It requires an additional variable declaration at the top of the macro's code:
Dim SubFolder As MAPIFolder
Another line sets the value of the SubFolder variable (this line must come after the line that sets the value of the Inbox variable):
Set SubFolder = Inbox.Folders("Sales Reports")
The code which scans for messages and attachments also needs to be modified by substituting the variable name SubFolder for the original variable name Inbox. The message text has been modified too. The changes and additions are marked in red:
If SubFolder.Items.Count = 0 Then MsgBox "There are no messages in the Sales Reports folder." _ , vbInformation, "Nothing Found" Exit Sub End If If SubFolder.Items.Count > 0 Then For Each Item In SubFolder.Items For Each Atmt In Item.Attachments FileName = "C:\Email Attachments\" & Atmt.FileName Atmt.SaveAsFile FileName i = i + 1 Next Atmt Next Item End If
Datestamp the Saved Files
The SaveAsFile method used to save an attachment will overwrite an existing file of the same name without warning. This might cause you problems! Suppose each day you received attached files that had the same name, and you ran the macro before you had dealt with yesterday's files? Or what if you hadn't run the macro for a couple of days and you had several mail messages, each containing different files but with the same filename?
The answer would be to give each saved file some sort of unique identifier that would set it apart from the others. There are several ways you can do this, each making use of the various properties of a mail item that you can retrieve using VBA. You could append some of this information to the filename to make it unique. You have lots of choices, and all you have to do is modify the line of code that generates the filename with which the attachment is saved - here are some suggestions...
The EntryID property of an Outlook mail item is a unique 48 character alphanumeric string assigned to the item when it arrives. It looks something like this:
Your code line would be (additions are marked in red):
FileName = "C:\Email Attachments\" & Item.EntryID & Atmt.FileName
As you can see, unless you choose to add just part of this string you end up with rather long filenames...
In fact there are three date/time properties that apply to each mail item, CreationTime, SentOn and ReceivedTime. Of these CreationTime is most likely to be unique, being the time at which the sender created the mail message to which the attachments belonged. All these properties are supplied as in the form of a full date and time stamp in the format mm/dd/yyyy hh:nn:ss. This is read by the code in this format complete with slashes and colons so needs formatting before it is incorporated into the filename. An example would be (additions are marked in red):
FileName = "C:\Email Attachments\" & _ Format(Item.CreationTime, "yyyymmdd_hhnnss_") & Atmt.FileName
Which would result in filenames sensibly timestamped and looking like this...
Show the User the Saved Files
You might like to offer the user the opportunity to view the saved files in Windows Explorer. This involves modifying the message near the end of the macro to include a question asking if the user wants to do this, and making use of the VBA Shellcommand to open the Windows Explorer program. First of all you need to add a variable declaration to those at the top of the macro code:
Dim varResponse As VbMsgBoxResult
NOTE: Office 97 does not have the VBA constant vbMessageBoxResult so if you are working with Office 97 use Variant instead.
This variable will hold the user's response when they click the Yes or No button on the modified message box. The modified code looks like this:
If i > 0 Then varResponse = MsgBox("I found " & i & " attached files." _ & vbCrLf & "I have saved them into the C:\Email Attachments folder." _ & vbCrLf & vbCrLf & "Would you like to view the files now?" _ , vbQuestion + vbYesNo, "Finished!") If varResponse = vbYes Then Shell "Explorer.exe /e,C:\Email Attachments", vbNormalFocus End If Else MsgBox "I didn't find any attached files in your mail.", vbInformation, _ "Finished!" End If
The message box code has been modified to display Yes and No buttons to the message box, as well as vbQuestion icon. An If Statement analyses the user's response and it is Yes it runs the line of code that opens Windows Explorer displaying the appropriate folder. The new message box looks like this...
Programming Outlook 97
Outlook 97 doesn't have the VBA Editor that accompanies the other Office 97 programs. This means that you can't program Outlook 97 from within Outlook itself. But Outlook 97 has a VBA object model and, thanks to VBA Automation (the process by which different programs talk to each other through VBA), you can create a macro in Excel, Access or Word that uses the same code as described in this tutorial. A few small modifications are required:
Set a Reference to the Outlook Object Model
Although this is primarily intended for users of Office 97, the same method can be applied to Office 2000 and Office 2002 if you want to run the macro from within another program.
Excel 97 or Word 97: Open the Visual Basic Editor in your chosen program and in the Project Explorer select the name of the file in which you want to create your macro (you might choose to use Personal.xls in Excel 97 or Normal.dot in Word 97). If necessary, add a code module as described above.
Access 97: This version of Access does not have the same Visual Basic Editor as the other programs but everything else works the same way. Instead, click the Modules tab of the Access database window. If you want to place your code in an existing module, double-click it to open it. Otherwise click the New button to open a new code module (remember to save it when prompted).
All Programs: Choose Tools > References to open the References dialog. Scroll down the list until you find Microsoft Outlook 8.0 Object Model and place a tick in the box next to it. Click the OK button. [Click the thumbnail below to see a full-sized image):
Setting a reference to the Outlook object model gives the current VBA Project access to Outlook's VBA library so that it understands and can check and execute your Outlook programming commands.
Add Commands to Communicate with Outlook
Enter an additional variable declaration at the beginning of the macro code to open a connection with Outlook:
Dim appOl As New Outlook.Application
Those variables which refer specifically to Outlook objects must now be edited so that the macro knows you are speaking to Outlook and not the host program:
Dim ns As Outlook.NameSpace Dim Inbox As Outlook.MAPIFolder Dim Atmt As Outlook.Attachment
The code assigning a value to the namespace variable needs to be modified:
Set ns = appOl.GetNamespace("MAPI")
Finally, an additional statement is required when clearing the memory:
Set appOl = Nothing
You can download a code module containing the entire macro code for Outlook 97 below.
About Outlook Security
Because of concerns about macro security, specifically from email viruses, various obstacles are put in the way of Outlook programmers. The macros described here will pose no threat to the security of your Outlook data and can be run on Outlook 97 with no further action. Outlook 2000 and 2002 however are equipped with various tools to monitor macro security.
Depending on your existing security settings, you may find that when you try and run your macro you see the following message:
You need to change the security settings to permit VBA code to be run. Choose Tools > Macro > Security to display the Security dialog. Set the security level to Medium and click OK.
After you do this, the first time in an Outlook session that you try to run a macro or open the Visual Basic Editor you see a warning message seeking your permission to run macros:
Choose Enable Macros to proceed. It is very unwise to set the security setting to Low!