Saving All Messages to the Hard Drive Using VBA

Last reviewed on December 30, 2013   —  91 comments

Use this code to save messages with the date in the filename, retaining the Outlook file structure.

To save selected messages as PDF files, see Save Outlook email as a PDF

This code sample will save all messages in a specific Outlook folder (and any subfolders of the selected folder) in a folder you select on the hard drive. The messages will be in a subfolder of the selected folder, where the subfolder is named for the Outlook folder you selected.

Note: if you select a subfolder of a top-level folder, for example, a subfolder of the Inbox, folder named Inbox needs to exist in path on the hard drive.

The filename format is yyyymmdd_hhmm_subject.msg, as in:

20100422_0319_Inquiry.msg

The filename is set using this code:

StrFile = StrSaveFolder & StrReceived & "_" & StrName & ".msg"

Filenames are limited to 256 characters in length, with the subject trimmed if its too long.

Note that it can take some time to run if the folder contains a lot of messages. Allow about 2 seconds per message, or about 15 minutes for 400 messages.

VBA Code

Click in the code area, press Ctrl+A to select all, Ctrl+C to copy then paste into Outlook's VBA editor. Instructions on using the editor are at How to use Outlook’s VBA Editor

Option Explicit
       Dim StrSavePath     As String

Sub SaveAllEmails_ProcessAllSubFolders()
      
    Dim i               As Long
    Dim j               As Long
    Dim n               As Long
    Dim StrSubject      As String
    Dim StrName         As String
    Dim StrFile         As String
    Dim StrReceived     As String
    Dim StrFolder       As String
    Dim StrSaveFolder   As String
    Dim StrFolderPath   As String
    Dim iNameSpace      As NameSpace
    Dim myOlApp         As Outlook.Application
    Dim SubFolder       As MAPIFolder
    Dim mItem           As MailItem
    Dim FSO             As Object
    Dim ChosenFolder    As Object
    Dim Folders         As New Collection
    Dim EntryID         As New Collection
    Dim StoreID         As New Collection
      
    Set FSO = CreateObject("Scripting.FileSystemObject")
    Set myOlApp = Outlook.Application
    Set iNameSpace = myOlApp.GetNamespace("MAPI")
    Set ChosenFolder = iNameSpace.PickFolder
    If ChosenFolder Is Nothing Then
GoTo ExitSub:
    End If
      
BrowseForFolder StrSavePath
         
    Call GetFolder(Folders, EntryID, StoreID, ChosenFolder)
      
    For i = 1 To Folders.Count
        StrFolder = StripIllegalChar(Folders(i))
        n = InStr(3, StrFolder, "\") + 1
        StrFolder = Mid(StrFolder, n, 256)
        StrFolderPath = StrSavePath & "\" & StrFolder & "\"
        StrSaveFolder = Left(StrFolderPath, Len(StrFolderPath) - 1) & "\"
        If Not FSO.FolderExists(StrFolderPath) Then
            FSO.CreateFolder (StrFolderPath)
        End If
          
        Set SubFolder = myOlApp.Session.GetFolderFromID(EntryID(i), StoreID(i))
        On Error Resume Next
        For j = 1 To SubFolder.Items.Count
            Set mItem = SubFolder.Items(j)
            StrReceived = Format(mItem.ReceivedTime, "YYYYMMDD-hhmm")
            StrSubject = mItem.Subject
            StrName = StripIllegalChar(StrSubject)
            StrFile = StrSaveFolder & StrReceived & "_" & StrName & ".msg"
            StrFile = Left(StrFile, 256)
            mItem.SaveAs StrFile, 3
        Next j
        On Error GoTo 0
    Next i
      
ExitSub:
      
End Sub
  
Function StripIllegalChar(StrInput)
    Dim RegX            As Object
      
    Set RegX = CreateObject("vbscript.regexp")
      
    RegX.Pattern = "[\" & Chr(34) & "\!\@\#\$\%\^\&\*\(\)\=\+\|\[\]\{\}\`\'\;\:\<\>\?\/\,]"
    RegX.IgnoreCase = True
    RegX.Global = True
      
    StripIllegalChar = RegX.Replace(StrInput, "")
      
ExitFunction:
    Set RegX = Nothing
      
End Function
  

Sub GetFolder(Folders As Collection, EntryID As Collection, StoreID As Collection, Fld As MAPIFolder)
    Dim SubFolder       As MAPIFolder
      
    Folders.Add Fld.FolderPath
    EntryID.Add Fld.EntryID
    StoreID.Add Fld.StoreID
    For Each SubFolder In Fld.Folders
        GetFolder Folders, EntryID, StoreID, SubFolder
    Next SubFolder
      
ExitSub:
    Set SubFolder = Nothing
      
End Sub
  
  
Function BrowseForFolder(StrSavePath As String, Optional OpenAt As String) As String
    Dim objShell As Object
    Dim objFolder '  As Folder

Dim enviro
enviro = CStr(Environ("USERPROFILE"))
Set objShell = CreateObject("Shell.Application")
Set objFolder = objShell.BrowseForFolder(0, "Please choose a folder", 0, enviro & "\Documents\")
StrSavePath = objFolder.self.Path

    On Error Resume Next
    On Error GoTo 0
      
ExitFunction:
    Set objShell = Nothing
      
End Function

About Diane Poremsky

Diane Poremsky
A Microsoft Outlook Most Valuable Professional (MVP) since 1999, Diane is the author of several books, including Outlook 2013 Absolute Beginners Book. She also created video training CDs and online training classes for Microsoft Outlook. You can find her helping people online in Outlook Forums as well as in the Microsoft Answers and TechNet forums.

Please post long or more complicated questions at Outlook forums by Slipstick.com.

91 responses to “Saving All Messages to the Hard Drive Using VBA”

  1. Tom

    Hey Diane, I tried your VBA code in Outlook 2010 and am getting the Run-time error '76': Path not found.

    I changed the code so that StrSavePath = "c:\Messages\" is a folder on my hard drive. The error stops at: "FSO.CreateFolder (StrFolderPath)"

    Any idea of why this would be happening?

    Thanks for your help!

    1. Diane Poremsky

      It sounds like the path does not exist - triple check for typos. Also, try a folder in my documents - there are sometimes problems writing to the c drive (permissions).

      One way to test the code is to remove the trailing slash -
      StrSavePath = "c:\Messages"

      The code should create the folder (and append the outlook folder name to it) - if that works, the folder path wasn't right.

  2. Travis

    Diane,
    I am having the same issue as Tom. I removed the trailing slash and also changed the path to my documents folder. the code still stops at the same place :
    FSO.CreatFolder (StrFolderPath)
    Any ideas?

    Thanks for all your help...

    1. Diane Poremsky

      press F8 to step through it - which line does it quit at?
      Which version of Outlook? I'll double check and make sure it's not missing a reference - as long as the main folder exists ( StrSavePath = "c:\Messages\") it should run without changing anything in Tools, references.

    2. Diane Poremsky

      What version of Outlook? Press F8 (over and over) to step through the macro. Which line does it stop on?

    3. Izbi

      Diane
      I have similar issues.
      The macro works perfectly for the INBOX and folders at the same level as the inbox.
      However if I choose a folder beneath this level, I get the error message "Run time error '76', Path not found.

    4. Izbi

      OK thank you again,

  3. Adrian Rutter

    Thanks Diane, really useful code.

    The issue experienced above appears to lie in creation of the folders if the user has not created the specified root folder for emails prior to executing. In my case, the first folder it tried to create was not Messages, but Messages\Inbox and if the parent folder does not exist, it cannot create the Inbox Sub-Folder...

    Simple fix, only direct the output to an existing folder.
    Code Fix, check and create the lower mailbox folder structure prior to the strFolderPathloop.

    It also only appears to run if you select the root folder of your mailbox, I select a subfolder, it does not build up the tree from the root.

    If I tried to extract my Friends folder (Mailbox\Inbox\Friends) it tries to create Messages\Inbox\Friends first and not Inbox to allow Inbox\Friends to be created.

  4. rich

    thanks diane - how can i edit your code to save as .pdf instead of .msg? Thanks!

    1. Diane Poremsky

      Because PDF is not a native format that Outlook saves as, you need to use the word object model to save as PDF. See save email as pdf for a code sample.

  5. HS

    Yes.... That is definitely the problem... Any solution?

  6. Nolberto Gaviria

    Thank you very much for sharing your knowledge. God bless you.

  7. M@rtin

    Hi Diane,

    thank you very much for this example!
    This is incredibly useful and nearly exactly what I was looking for!

    Unfortunately, however, for me the date string produces strange results. I assume that this is, because I'm working on Windows 7 with Office 2007 and German language/date settings.

    I'm not an expert at all, so maybe this is a stupid question, but the ArrangedDate function in your code looks quite complicated to me.
    Wouldn't it be easier (and maybe more foolproof) to use something like Format(objmail.ReceivedTime, "YYYY-MM-DD hh:mm")?

    Kind regards

    Martin

    1. Diane Poremsky

      It is likely because you are using German or at least using a different date format. You can change it to use whatever format you want - I'm just providing examples of what is possible. :)

  8. M@rtin

    p.s. for me it seems to work, if you replace the line
    StrReceived = ArrangedDate(mItem.ReceivedTime)
    by
    StrReceived = Format(mItem.ReceivedTime, "YYYYMMDD-hhmm").

    The date format "YYYYMMDD-hhmm" could then be defined according to the vba conventions.

    Another thing that came to my mind:
    it would be nice to remove forbidden characters (forbidden regarding filenames ion the different operating systems!) from the subject.
    I assume there exists ready-to-run functions which take a string and remove all frobidden characters or replace them by something else like "_").

    Kind regards

    Martin

    1. Diane Poremsky

      The StripIllegalChar function should remove all invalid characters - you could change it to replace them:
      StripIllegalChar = RegX.Replace(StrInput, "_")

      There is another function here that uses vba, not regex, to cleanup filenames.

    2. Diane Poremsky

      BTW, thanks for bringing this sample to my attention. It had more goofiness than just the received date. It was one of the code samples that was left on the site when OutlookCode split off and I think I know why it was left behind, it was a piece of... :)

  9. M@rtin

    thanks for your quick replies andn for the changes in the code!

    There is one more question/wish from my side:
    How could the code be changed to act only on the current selection?

    Kind regards

    1. Diane Poremsky

      See for a macro that saves the selected message (or messages) as .msg files. It doesn't recreate the folder path though.

  10. Phuc Dinh Cong

    Thank the Author. Here is my debug, it works even not perfect coding

    text file containing macro

  11. Philipp Post

    Have cleaned up the variable names, fixed the issue with folders which could not be created, fixed stripping just the filename and not the whole path, char replacement rewritten. Thanks a lot to Diane for posting the initial idea!
    text file containing macro

    1. Diane Poremsky

      Thanks for sharing! A text file containing the code is available here.

  12. Tony Rockdaschel

    I'd love to get this code working but I get a syntax error for this part using Philip's code above. Can you help me figure out why?

    'Clean out invalid chars
    outlook_folder_path = Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(outlook_folder_path, "", ""), _
    ":", ""), _
    """", ""), _
    "/", ""), _
    "|", ""), _
    "?", ""), _
    "*", "")

    1. Diane Poremsky

      try adding a second ) after the very last one.

  13. Andrzej

    Hey - I had the same problem, and I think the backslash was simply omitted in the list of characters to be replaced... I changed the section to the following and now it works.

    'Clean out invalid chars
    outlook_folder_path = Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(outlook_folder_path, "", ""), _
    ":", ""), _
    """", ""), _
    "\", ""), _
    "/", ""), _
    "|", ""), _
    "?", ""), _
    "*", "")

    Now the code runs quite well - thanks so much to everyone that contributed! Only one problem remains - after a couple of hundred emails saved, I get the -2147286788 error, which apparently stands for "(800300FC) The name %1 is not valid." Any idea why that happens and how it can be fixed?

    1. Diane Poremsky

      Have you identified the message that is tripping it up? I'm guessing there is a character, possibly a non-printable character, that is tripping it up. The code at http://www.slipstick.com/developer/code-samples/save-outlook-email-pdf/ has a longer list of invalid characters (some are valid in windows but cause problems in sharepoint) but before you try stripping more characters, add a debug.print to see what it getting processed.

      This should capture the message subjects before the error is triggered. Open the VB Editor and press Ctrl+G to see the list.
      Set mItem = SubFolder.Items(j)
      debug.print mItem '(or mitem.subject)

    2. Andrzej

      Can't thank you enough for your swift help, Diane... that Debug.Print hint helped me solve the problem... a double-space in a file name was the issue, I added a few more lines to the character replacement in the filenames (to include all the characters from the PDF script, and potential double- and triple spaces in the subject line), and now the script works like a charm, even on a monster folder with close to 6,000 emails. Thanks to you and Philipp's efforts!

      See here for the modified section that should help with potential character issues, maybe you can change that in the downloadable .txt file as well?

      filename = Replace(filename, "", replacing_char)
      filename = Replace(filename, ":", replacing_char)
      filename = Replace(filename, """", replacing_char)
      filename = Replace(filename, " ", replacing_char)
      filename = Replace(filename, " ", replacing_char)
      filename = Replace(filename, "/", replacing_char)
      filename = Replace(filename, "\", replacing_char)
      filename = Replace(filename, "|", replacing_char)
      filename = Replace(filename, "?", replacing_char)
      filename = Replace(filename, "*", replacing_char)
      filename = Replace(filename, "", replacing_char)
      filename = Replace(filename, "&", replacing_char)
      filename = Replace(filename, "%", replacing_char)
      filename = Replace(filename, "{", replacing_char)
      filename = Replace(filename, "[", replacing_char)
      filename = Replace(filename, "]", replacing_char)
      filename = Replace(filename, "}", replacing_char)
      filename = Replace(filename, "!", replacing_char)

  14. Tony Rockdaschel

    It ended up that I had one more "Replace" than I had invalid characters to be replaced. I removed one Replace and all is well.

    1. Diane Poremsky

      I didn't write that code but every time I look at it I think I should replace it with an easier-to-read replace character function. :)

  15. Philipp Post

    I do think so too, Diane. - Thanks to all for the feedback. In fact the issue was not within the Replace but a leftover from the old code which cause paths with double \\. Fixed that and changed replacement of illegal chars to REGEX. Brgds Philipp
    Macro sample

  16. Andrzej

    Thanks to you, Philipp and Diane, for all the hints and changes!
    One next issue came up, though - while the script worked well on the first ~6000 messages I ran it on, I tried using it on the next batch of messages and got the error message: "Object doesn't support this property or method (438)" - any idea where this comes from? I got it with both my revised v1 of Philipp's script and the v2 he posted above. I tried to debug.print, but the error comes before/after various types of emails, and removing these does not seem to solve the problem. Thanks for any advice y'all might have!

    1. Diane Poremsky

      Is the message signed? What attachments are on the message? Do they have special characters in the the filename?

  17. Andrzej

    I finally found the error, using the Debug.Print wrongly kept me from finding it faster... it was a mail item that consisted of a Word Document - seems like DOC was not attached, but the email itself (probably sent from within Word). That's why the script failed in grabbing the received time, subject etc. and crashed. All good now. Thanks again!

  18. Philipp Post

    You could try the following to trace the issue: out comment (put a ' in front of) "On Error GoTo error_handler", then run the procedure till the error ocurrs and press debug. Look at what code line the error comes up and post this back, pls. You can hover with them mouse over the variable names to see their values (is some of them wrong or empty?) Further analyze the mail item which caused the error as Diane said.

  19. Thom

    The error occurs every time it encounters a read receipt.

    1. Diane Poremsky

      Ah. On Error Resume Next after the DIM and Set statements should fix it, or add an If statement -
      If mItem.messageclass = ipm.note then
      ' do the save
      else
      end if
      next j

  20. Philipp Post

    The issue is just with the .ReceivedTime which we cannot get from a read receipt (which is of MessageClass "Report.IPM.Note.IPNRN")

    Have fixed that and added saving the message in other formats as well and then saving the attachements separately. PDF via Word works, but is slow on lots of messages and does not save embedded pictures.
    Macro sample

    1. Diane Poremsky

      Do you need to save read receipts? If you only want messages, use an if statement.

      if outlook_mail_item.messageclass = "ipm.note" then
      ' do the save
      end if

      on slow pdf's, see Javier's comment - if you can keep word open, it should be faster.

  21. Thom

    Shouldn't
    disk_folder_path = disk_root_path & outlook_folder_path & "\"
    be
    disk_folder_path = disk_root_path & "\" & outlook_folder_path & "\"

    1. Diane Poremsky

      it depends if the disk_root_path has the ending \.

  22. eric

    Hi Diane,

    If I use your code on a lot of large e-mails it might take some time , that's why i would like to check if the filename does not already exist in the selected folder in order not to resave e-mail unnecessarily and also to be able to save e-mails to a server used by multiple people saving in the same folder,

    The code i had in mind would be something like this

    Set MyObject = New Scripting.FileSystemObject
    Set mySource = MyObject.GetFolder(StrSaveFolder)
    On Error Resume Next
    For Each myfile In mySource.Files
    If myfile.Name = StrFile Then
    MsgBox "It's working!"
    GoTo SaveTime
    End If

    Next

    mItem.SaveAs StrFile, 3
    SaveTime:

    Unfortunately this only works in vba-excell, I don't know how to make it work in outlook.

    Any ideas?

    Tanks

    1. Diane Poremsky

      It uses FSO, so if it works in excel, it will work in Outlook. What error message do you get?

  23. eric

    Compile error:

    variable not defined

    with myobject highlighted.

  24. eric

    Ok, I understand why it's not working, didn't see the option explicit, however, i'm kind of a bad in object defining and I don't know how to dimension "mysource" and "myfiles".

    Do you know how to make this work.

  25. eric

    OK, I defined mysource as a folder and myfiles as a file, everything is working, the code is running way faster now... sorry for the multiple posts, you can remove them if you want and just post the final solution, if you agree it's an good upgrade to your code of course.

    Anyway, many thanks for the code, this is just what I needed!

    1. Diane Poremsky

      Thanks for the update. I'll leave the other posts, it may help someone who is having problems getting their code to work.

  26. Kris

    There's a wee typo here in Phillips post

    If mail_file_type "msg" Then

    should this be

    If mail_file_type = "msg" Then

  27. Kris

    1. Could someone please post's eric complete solution for speeding it up please?
    2. If you do not want to have the macro save atatchments out, then in Philips last post delete this section:

    'Save attachements where the format does not do it automatically
    If mail_file_type "msg" Then

    'Go through all attachments
    For mail_attachment_counter = 1 To outlook_mail_item.Attachments.Count

    'Get the attachment file name from Outlook
    mail_attachment_filename = outlook_mail_item.Attachments(mail_attachment_counter).filename
    'Separate file extension and file name
    mail_attachment_extension = Right(mail_attachment_filename, Len(mail_attachment_filename) - InStrRev(mail_attachment_filename, "."))
    mail_attachment_filename = Left(mail_attachment_filename, InStrRev(mail_attachment_filename, ".") - 1)
    'Shorten overlong filenames
    mail_attachment_filename = Left(mail_attachment_filename, 50)
    'Cleanup the file name
    mail_attachment_filename = Remove_Illegal_Characters(mail_attachment_filename)
    'Outlook permits to add the same file name twice to a message.
    'Put the filename back together and add a counter to be unique
    mail_attachment_filename = mail_attachment_filename & "_" & mail_attachment_counter & "." & mail_attachment_extension

    'Use the mail file name (without extension) plus the attachment name separated by -- as file name
    '(otherwise it would be unclear to which message the attachment belongs)
    outlook_mail_item.Attachments(mail_attachment_counter).SaveAsFile mail_file_fullname & "--" & mail_attachment_filename

    Next

    End If

    ===================================================================

    1. eric

      Hi Kris
      Here's my entire code to go faster and to not have duplicates:

      I go faster by checking if the file already exists (for each folder and subfolder), if the e-mail is already saved then it will not be overwritten.

      Macro code

  28. Kris

    Final comment / request2 for the day:

    1. If you run it on the same folder, it will not overwrite (or ignore for speed) the existing msg, it will create a new one? Anyone have a solution to get around that?

    2. Second request, for dates to be at the start of the message, do i just invert this from

    mail_file_fullname = disk_folder_path & mail_filename & "_" & mail_received_time & "_" & mail_counter

    to

    mail_file_fullname = mail_received_time & disk_folder_path & mail_filename & "_" & "_" & mail_counter

    ?

    Thanks!

    Kris

    1. Diane Poremsky

      1. You'd need to check for duplicate filenames before you create the file. Or, add a value or category to the message and skip items in the category or with a certain value in a custom field.

      2. Yes, you just need to change the order of the fields when the name is constructed.

  29. JAIME

    Hi ! i have a problem with this code, i already have the directory to save the emails, and i know wich folder in outlook i want to select.

    where i can replace this information ?

    i think i have to change this:

    Set ChosenFolder = iNameSpace.PickFolder

    for this:

    Set ChosenFolder = iNameSpace.folders("personal folders").folders("inbox")

    this solve the folders problem.

    and change this:

    BrowseForFolder StrSavePath

    for:

    strsavepath = "C:\test\"

    for solve the directory problem.

    Now im having problem with this new code, because in my computer (outlook 2013) its working, but in my friend computer (outlook 2007) isnt working. how i can fix this ?

    im trying to save the last message (most recent) to the hard drive in .msg format. THANKS ! C:

  30. Bavaria

    Just for your Information: one thing to mention:

    "Problem": If there are more emails (with "nearly" identical conditions but definetely no dublicates, which result in the same identical StrFile) in a email older, then only one email is created as file!

    But thank you very much for the code!

    I have modified your code and solved this "problem" with this code:
    CUT
    StrFile = Left(StrFile, 256)
    If Dir(StrFile, vbNormal) "" Then
    StrFileNeu = Left(StrFile, InStrRev(StrFile, ".") - 1) & "-" & j & ".msg"
    mItem.SaveAs StrFileNeu, 3
    Else
    mItem.SaveAs StrFile, 3
    End If
    Next j
    CUT

    1. Diane Poremsky

      Yeah, if there are messages that will create identical file names, you need to do something to make them unique. One of my scripts here adds the received date and time to avoid problems.

  31. Bavaria

    Dear Diane, thank you very much for your work, your time and not only for this code and your time for comments.

    (In my very special case: the received date and time was not enough - maybe the time in seconds could work - but I choosed just the "j" in the "For" loop to get a simple unique number added to the names".)

    1. Diane Poremsky

      the advantage of using a number rather than the date is size - the full date and time adds 14 characters, more if you use separators. You could use the current time serial for the code, it's still 6 (or 8?) digits. A for loop that increments would result in shorter file names.

  32. Bavaria

    ok, to show you, in my case it is even more Information what I liked to have (but I did not like that for resulting identical names only one file was created, therefore I used the "j" as number to see "duplicated emails"):

    For j = 1 To SubFolder.Items.Count
    Set mItem = SubFolder.Items(j)
    StrSenderEmail = mItem.SenderEmailAddress
    StrRecipiEmail = mItem.To
    StrReceived = Format(mItem.ReceivedTime, "YYYYMMDD-hhmm")
    StrSubject = mItem.Subject
    StrName = StripIllegalChar(StrSubject)
    StrFile = StrSaveFolder & StrReceived & "_" & StrSenderEmail & "__" & StrRecipiEmail & "__" & StrName & ".msg"
    StrFile = Left(StrFile, 256)
    If Dir(StrFile, vbNormal) "" Then
    StrFileNeu = Left(StrFile, InStrRev(StrFile, ".") - 1) & "-" & j & ".msg"
    mItem.SaveAs StrFileNeu, 3
    Else
    mItem.SaveAs StrFile, 3
    End If
    Next j

    To explain why I need your code: I worked for more than 15 years very happy with Outlook 2000 and kept my PST-Files always under 2GB (handled them like raw eggs, used backups, copies and so on) and "maintained" them well with SCANPST.EXE. Now I changed to OUTLOOK 2013, made new PST-Files with new OL2003andup-Format and copied the content from the old OL2000-PST-Files into them. The thing/problem is that always if I use the NEW SCANPST.EXE it reports "minor inconsistencies". Also if I use it on a brandnew PST-File.
    I use your code because on/for the side of the file system with the created .msg files I have more tools to compare and analyze them than when I am in OUTLOOK. And I have msg-copies/backups of my old emails also. Few days before I had written you an email because I think there is a problem with scanpst.exe, and followed also your other comments on this topic on other places in the web. ..I´m in the process to analyze why this happens.

  33. Galen

    Dianne,
    I have your original code at the top of the page working for my needs except I would like to add one additional item and I am not sure how to do it...
    After the Date entry, and before the Subject in the new file name, I would also like to include the sender's name separated from the date by a space, likewise before the subject. Can you advise what this extra code should look like?

    1. Diane Poremsky

      This is where the file name is put together:
      StrFile = StrSaveFolder & StrReceived & "_" & StrName & ".msg"

      You'd add mItem.sender to it.
      StrFile = StrSaveFolder & mItem.sender & " " & StrReceived & "_" & StrName & ".msg"

  34. Galen

    Thanks a million for the assistance. I will give it a try in the morning.

  35. Galen

    I tried the fix listed above and encountered a couple of problems with this approach as is. I was, however, able to see what needed to be changed and it now works for what I want... mostly...

    StrFile = StrSaveFolder & StrReceived & " " & mItem.SenderName & " " & StrName & ".msg"

    This puts the email sender's name after the time/date stamp and separates it from the stamp and the subject by a couple of spaces.
    The only problem I can see is that this will probably pull in my email name as the sender for all "Sent" messages rather than providing me with the recipients name. I think I can figure that out and use some sort of logic statement to pull in the correct field.

    Thanks, again, for getting me on the correct path. (pardon the pun.)

    1. Diane Poremsky

      Sent messages will be your name - see http://www.slipstick.com/developer/recipient-email-address-sent-items/ for the bits of code needs to get the recipient information. Use .Name instead of .address - you'll need to clean it the same way the subject is stripped of illegal characters.

  36. Paddy

    Dear Diane,

    thank you for the great script that really shortened my own development time, which is rather down to trial and error.

    What I'm looking for now is a script to reverse what the one above just did, which means re-integrate msg files in a folder and subfolders in my Outlook archive.
    Select a folder with msg files and other folders on the HD, create folders in the Outlook archive, according to the ones found in the "master" folder, copy all msg files and so on,

    I used the script found here (http://www.slipstick.com/developer/open-outlook-messages-stored-filesystem/), which is already a great basis. Howver, the final step from opening the msg file and storing it somewhere in Outlook won't work. Any ideas?

    Cheers
    Paddy

    1. Diane Poremsky

      How are you trying to get them back in? Haven't verified it will work, but I'd use (or try :)) move:

      mItem.move saveFolder

      display the message, move it to a folder and then save it.

  37. Paddy

    If you take a look at the script I mentioned above, I shortened down the "action" part to:

    If strFileType = ".msg" Then
    Set openMsg = Application.CreateItemFromTemplate(FileItem.Path)
    openMsg.Display
    openMsg.Copy (DestiNation.FolderPath)
    openMsg.Close olDiscard
    Set openMsg = Nothing
    End If

    The problem is the .copy line. DestiNation is a MAPIFolder and generated from a simple string using the following function. It works in other contexts, but not this time.

    Public Function GetFolder(ByVal FolderPath As String) As Outlook.folder
    Dim TestFolder As Outlook.folder
    Dim FoldersArray As Variant
    Dim i As Integer
    On Error GoTo GetFolder_Error
    If Left(FolderPath, 2) = "\\" Then
    FolderPath = Right(FolderPath, Len(FolderPath) - 2)
    End If
    If Right(FolderPath, 1) = "\" Then
    FolderPath = Left(FolderPath, Len(FolderPath) - 1)
    End If
    'Convert folderpath to array
    FoldersArray = Split(FolderPath, "\")
    Set TestFolder = Application.Session.Folders.item(FoldersArray(0))
    If Not TestFolder Is Nothing Then
    For i = 1 To UBound(FoldersArray, 1)
    Dim SubFolders As Outlook.Folders
    Set SubFolders = TestFolder.Folders
    Set TestFolder = SubFolders.item(FoldersArray(i))
    If TestFolder Is Nothing Then
    Set GetFolder = Nothing
    End If
    Next
    End If
    'Return the TestFolder
    Set GetFolder = TestFolder
    Exit Function
    GetFolder_Error:
    Set GetFolder = Nothing
    Exit Function
    End Function

    In the end however, it returns error 440, "object does not support the method". I must have declared something wrong, and this is exactly where my VBA knowledge comes to a sudden end... Any suggestions?

    1. Diane Poremsky

      I think it's with this:
      openMsg.Copy (DestiNation.FolderPath)

      Are you putting them back in the same path in outlook? I'm getting object variable on the destination folder.

      However, a bigger problem is that the messages are opening as drafts, not messages.

    2. Diane Poremsky

      this might be one option: http://www.outlookcode.com/threads.aspx?forumid=4&messageid=26038

      ETA I'm getting the same result with redemption - a draft, not the message as a received message. some were in the outbox, which will generate an error as they can't be resent.

  38. mhegeral@live.com

    thank you very much. the first worked for me. i just needed to creat the target folder.

  39. santinimatias

    Diane,
    For the longest time I've been looking for a code like this one, Thank you so much!.
    In my office we typically save emails in a "Project" correspondence folder (with their correspondent sub-folders). We also have a filename system that shows as follows:

    E 14-1007 1
    E 14-1007 2
    E 14-1007 3
    E 14-1008 1
    E 14-1008 2
    E 14-1009 1
    E 14-1010 1

    As you see, we have an "E " + mail date received + counter.
    All this is done by "hand", by drag and dropping from Outlook to the target folder....taking hours and hours....

    I was already able to modify the filename standard to look as follows:
    E 14-1007 1
    E 14-1007 2
    E 14-1008 3
    E 14-1008 4

    (counter keeps going, no matter the date)

    The first thing I'm trying to do is to have the counter to be related to the date, so for each new date in the filename then the counter starts over.
    The second goal is to have the code to check in the correspondence folder if the filename already exists, so it can save the email with the next logical counter number.

    for example, if the files below already exists in the target folder:
    E 14-1007 1
    E 14-1007 2
    E 14-1007 3

    and I need to save other emails with the same date, it would save them as follows:

    E 14-1007 4
    E 14-1007 5
    E 14-1007 6

    and so on.......

    I am not a programmer, so any help would be greatly welcome.

    Here is the code I have: (I'm aware there are parts of the code that will NOT be needed anymore, since the filename is so specific).

    Macro code

    1. Diane Poremsky

      The first thing I'm trying to do is to have the counter to be related to the date, so for each new date in the filename then the counter starts over.

      As long as you don't need to keep track of dates, it shouldn't be too tough to do. ie, dates are done in order and when you move to the next day, you won't have any more messages for the day before.

      Set a variable for the received date of the first message and compare it to the mail_recieved_time variable. When DD is up 1, restart the counter. something along these lines:
      start_date = format(outlook_mail_item.ReceivedTime, "DD")
      received_date = Format(outlook_mail_item.ReceivedTime, "DD")
      if received_date = start_date then
      ' keep incrementing
      else
      counter = 1
      end if

      The second goal is to have the code to check in the correspondence folder if the filename already exists, so it can save the email with the next logical counter number.

      if the macro is erroring if the filename exists, this is easy - use an error handler to change the filename by one. If it doesn't error, you'll need to check for the filename and raise the count if found.

  40. Timothy Dollimore

    One thing I like to be able to do is timestamp the messages so they physically show up in the file system with their receive date.

    http://www.freevbcode.com/ShowCode.asp?ID=1335

    Create an extra module with this in it
    Option Explicit
    'http://www.freevbcode.com/ShowCode.asp?ID=1335
    'Change a File's Last Modified Date Stamp
    'Category:Files and Directories
    'Type:Snippets
    'Difficulty:Intermediate
    'Author: Intelligent Solutions Inc.

    Private Type FILETIME
    dwLowDate As Long
    dwHighDate As Long
    End Type

    Private Type SYSTEMTIME
    wYear As Integer
    wMonth As Integer
    wDayOfWeek As Integer
    wDay As Integer
    wHour As Integer
    wMinute As Integer
    wSecond As Integer
    wMillisecs As Integer
    End Type

    Private Const OPEN_EXISTING = 3
    Private Const FILE_SHARE_READ = &H1
    Private Const FILE_SHARE_WRITE = &H2
    Private Const GENERIC_WRITE = &H40000000

    Private Declare Function CreateFile Lib "kernel32" Alias _
    "CreateFileA" (ByVal lpFileName As String, _
    ByVal dwDesiredAccess As Long, _
    ByVal dwShareMode As Long, _
    ByVal lpSecurityAttributes As Long, _
    ByVal dwCreationDisposition As Long, _
    ByVal dwFlagsAndAttributes As Long, _
    ByVal hTemplateFile As Long) _
    As Long

    Private Declare Function LocalFileTimeToFileTime Lib _
    "kernel32" (lpLocalFileTime As FILETIME, _
    lpFileTime As FILETIME) As Long

    Private Declare Function SetFileTime Lib "kernel32" _
    (ByVal hFile As Long, ByVal MullP As Long, _
    ByVal NullP2 As Long, lpLastWriteTime _
    As FILETIME) As Long

    Private Declare Function SystemTimeToFileTime Lib _
    "kernel32" (lpSystemTime As SYSTEMTIME, lpFileTime _
    As FILETIME) As Long

    Private Declare Function CloseHandle Lib "kernel32" _
    (ByVal hObject As Long) As Long

    Public Function SetFileDateTime(ByVal FileName As String, _
    ByVal TheDate As String) As Boolean
    '************************************************
    'PURPOSE: Set File Date (and optionally time)
    ' for a given file)

    'PARAMETERS: TheDate -- Date to Set File's Modified Date/Time
    ' FileName -- The File Name

    'Returns: True if successful, false otherwise
    '************************************************
    If Dir(FileName) = "" Then Exit Function
    If Not IsDate(TheDate) Then Exit Function

    Dim lFileHnd As Long
    Dim lRet As Long

    Dim typFileTime As FILETIME
    Dim typLocalTime As FILETIME
    Dim typSystemTime As SYSTEMTIME

    With typSystemTime
    .wYear = Year(TheDate)
    .wMonth = Month(TheDate)
    .wDay = Day(TheDate)
    .wDayOfWeek = Weekday(TheDate) - 1
    .wHour = Hour(TheDate)
    .wMinute = Minute(TheDate)
    .wSecond = Second(TheDate)
    End With

    lRet = SystemTimeToFileTime(typSystemTime, typLocalTime)
    lRet = LocalFileTimeToFileTime(typLocalTime, typFileTime)

    lFileHnd = CreateFile(FileName, GENERIC_WRITE, _
    FILE_SHARE_READ Or FILE_SHARE_WRITE, ByVal 0&, _
    OPEN_EXISTING, 0, 0)

    lRet = SetFileTime(lFileHnd, ByVal 0&, ByVal 0&, _
    typFileTime)

    CloseHandle lFileHnd
    SetFileDateTime = lRet > 0

    End Function

    Then change the SaveAllEmails_ProcessAllSubFolders routine
    like so (I've changed the timestamp in the message so its at the end; using human readable format; and wrapped in parentheses)

    Dim StrReceived As String
    Dim StrSent As String \\ additional variable to hold good timestamp
    Dim StrFolder As String

    For j = 1 To SubFolder.Items.Count
    Set mItem = SubFolder.Items(j)
    StrReceived = Format(mItem.ReceivedTime, "hhmmss DD MMM YYYY")
    StrSent = Format(mItem.ReceivedTime, "hh:mm:ss DD/MM/YYYY") // passed to SetFileDateTime
    StrSubject = mItem.Subject
    StrName = StripIllegalChar(StrSubject)
    StrFile = StrSaveFolder & StrName & " (" & StrReceived & ").msg"
    StrFile = Left(StrFile, 256)
    mItem.SaveAs StrFile, 3
    SetFileDateTime StrFile, StrSent // Et voila! now the messages have the time as their last modified date (in addition to the title )
    Next j

  41. Timothy Dollimore

    Um, probably shouldn't use // as the inline comment marker for VBA, doh! :-)

  42. 1h249s8

    Hi, I've upgraded the above code to satisfy my needs, I'm posting it below, main changes are:

    1. Macro goes faster because it does not save the same e-mail twice, it checks if the email already exists then skips to the next one.
    2. Macro assigns a folderpath to each archive folder and saves it.
    3. Macro functions with the selected folder, simply select the folder then click the macro (easy if it is in the quick acces toolbar of outlook.)

    ///////
    CODE
    ///////

    Option Explicit

    ' This macro saves all e-mails to specific folders on the hard drive. these specific folders are defined by the user,
    ' once the path of the folder has been entered it is saved in the description of the archive folder, the macro will access this information
    ' again if the user wants to save new e-mails stored in this archive folder
    '
    ' E-mails that have been saved already are not saved re-saved, the macro verifies the presence of older e-mails (see lines with namelist)
    ' IN ORDER TO CHANGE PATH OF THE HARD-DRIVE FOLDER, RIGHT-CLICK ON THE ARCHIVE AND SELECT PROPERTIES, THEN REPLACE THE PATH IN THE DESCRIPTION

    ' DEVELOPPED BY SLIPSTICK TWEAKED BY ERICDS

    Sub SaveEmailFOLDER_ProcessAllSubFolders()
    Dim st As Currency, et As Currency
    st = myTimer

    Dim Employees As Collection

    Set Employees = New Collection

    Dim i As Long
    Dim j As Long
    Dim n As Long
    Dim StrSubject As String
    Dim StrName As String
    Dim StrFile As String
    Dim StrReceived As String
    Dim StrSavePath As String
    Dim StrFolder As String
    Dim StrFolderPath As String
    Dim StrSaveFolder As String
    Dim Prompt As String
    Dim Title As String
    Dim StrSender As String
    Dim iNameSpace As NameSpace
    Dim myOlApp As Outlook.Application
    Dim SubFolder As MAPIFolder
    Dim mItem As MailItem
    Dim FSO As Object
    Dim ChosenFolder As Object
    Dim Folders As New Collection
    Dim EntryID As New Collection
    Dim StoreID As New Collection
    Dim myObject As Object
    Dim mySource As Object
    Dim myFile As Object
    Dim p As Long
    Dim NameList() As String
    Dim Count As Long
    Dim MailsAdded As Long
    Dim InputPath As Variant

    Dim fld As Outlook.MAPIFolder
    Set fld = Application.ActiveExplorer.CurrentFolder

    p = 0

    'Set myObject = CreateObject("Scripting.FileSystemObject")
    Set FSO = CreateObject("Scripting.FileSystemObject")
    Set myOlApp = Outlook.Application
    Set iNameSpace = myOlApp.GetNamespace("MAPI")
    'Set ChosenFolder = iNameSpace.PickFolder
    Set ChosenFolder = fld
    If ChosenFolder Is Nothing Then
    MsgBox "Please select archive folder!"
    GoTo ExitSub:
    End If

    StrSavePath = ChosenFolder.Description

    If StrSavePath = "" Then
    Enterpath:
    InputPath = InputBox("No folder assigned yet, please enter the folderpath. Example : G:\DGP\P_007436_KHAOKORWINDF\MAIL")
    ChosenFolder.Description = InputPath
    If InputPath = "" Then
    GoTo ExitSub:
    End If

    StrSavePath = ChosenFolder.Description

    End If

    If Not FileFolderExists(StrSavePath) Then
    MsgBox StrSavePath & " - Le fichier n'existe pas ou mauvaise adresse dans vba!"
    GoTo Enterpath:
    End If

    If Not Right(StrSavePath, 1) = "\" Then
    StrSavePath = StrSavePath & "\"
    End If

    Call GetFolder(Folders, EntryID, StoreID, ChosenFolder) ' All subfolders of outlook and the main folder are checked for e-mails

    For i = 1 To Folders.Count

    StrFolder = StripIllegalChar(Folders(i))
    'MsgBox i & " " & StrFolder
    n = InStr(3, StrFolder, "\") + 1
    StrFolder = Mid(StrFolder, n, 256)
    StrFolderPath = StrSavePath '& StrFolder & "\" ' I do not use strfolder, all subfolders in outlook are saved in the same folder on the hard drive
    'MsgBox StrFolderPath
    'MsgBox StrFolder
    'MsgBox i & " " & StrFolder

    StrSaveFolder = Left(StrFolderPath, Len(StrFolderPath) - 1) & "\"

    If Not FSO.FolderExists(StrFolderPath) Then
    FSO.CreateFolder (StrFolderPath)
    End If

    Set SubFolder = myOlApp.Session.GetFolderFromID(EntryID(i), StoreID(i))
    Set mySource = FSO.GetFolder(StrSaveFolder)
    On Error Resume Next
    On Error Resume Next

    If NameList(0) = "" Then ' the list is only made once for each subfolder in outlook
    'MsgBox "ok"

    ReDim NameList(0 To mySource.Files.Count)
    For Each myFile In mySource.Files
    'MsgBox Employees.Item(1)
    NameList(Count) = myFile.Name
    Count = Count + 1
    Next
    End If

    For j = 1 To SubFolder.Items.Count

    Set mItem = SubFolder.Items(j)
    StrReceived = StripIllegalChar(Left(mItem.ReceivedTime, 10))
    StrReceived = Format(mItem.ReceivedTime, "YYYYMMDD-hhmm")
    StrSender = Left(mItem.SenderName, 15)
    StrSubject = mItem.Subject
    StrName = StripIllegalChar(StrSubject)
    StrFile = StrSaveFolder & StrReceived & "-" & StrSender & "_" & StrName & ".msg"
    'MsgBox StrFile

    StrFile = Left(StrFile, 256)

    'MsgBox mySource.Name

    For p = 0 To Count
    'If Employees.Item(p) = StrReceived & "-" & StrSender & "_" & StrName & ".msg" Then

    'MsgBox "trouvé"
    'Employees.Remove (p)
    'GoTo SaveTime
    'End If

    If NameList(p) = StrReceived & "-" & StrSender & "_" & StrName & ".msg" Then
    GoTo SaveTime
    End If

    Next p

    MailsAdded = MailsAdded + 1
    mItem.SaveAs StrFile, 3
    SaveTime:
    Next j
    On Error GoTo 0
    Next i
    et = myTimer
    'MsgBox Format(myElapsedTime(et - st), "0.000000") & " seconds"
    If MailsAdded = 0 Then
    MsgBox "Folder was already up to date!"
    Else
    MsgBox MailsAdded & "/" & Count & " mails added to folder in " & Format(myElapsedTime(et - st), "0.000") & " seconds " & vbNewLine & " Folder is up to date!"
    End If

    ExitSub:

    End Sub

    Function StripIllegalChar(StrInput)

    Dim RegX As Object

    Set RegX = CreateObject("vbscript.regexp")

    RegX.Pattern = "[\" & Chr(34) & "\!\@\#\$\%\^\&\*\(\)\=\+\|\[\]\{\}\`\'\;\:\\?\/\,]"
    RegX.IgnoreCase = True
    RegX.Global = True

    StripIllegalChar = RegX.Replace(StrInput, "")

    ExitFunction:

    Set RegX = Nothing

    End Function

    Sub GetFolder(Folders As Collection, EntryID As Collection, StoreID As Collection, fld As MAPIFolder)

    Dim SubFolder As MAPIFolder

    Folders.Add fld.FolderPath
    EntryID.Add fld.EntryID
    StoreID.Add fld.StoreID
    For Each SubFolder In fld.Folders
    GetFolder Folders, EntryID, StoreID, SubFolder
    Next SubFolder

    ExitSub:

    Set SubFolder = Nothing

    End Sub

    Function BrowseForFolder(Optional OpenAt As String) As String

    Dim ShellApp As Object

    Set ShellApp = CreateObject("Shell.Application"). _
    BrowseForFolder(0, "Please choose a folder", 0, OpenAt)

    On Error Resume Next
    BrowseForFolder = ShellApp.self.Path
    On Error GoTo 0

    Select Case Mid(BrowseForFolder, 2, 1)
    Case Is = ":"
    If Left(BrowseForFolder, 1) = ":" Then
    BrowseForFolder = ""
    End If
    Case Is = "\"
    If Not Left(BrowseForFolder, 1) = "\" Then
    BrowseForFolder = ""
    End If
    Case Else
    BrowseForFolder = ""
    End Select

    ExitFunction:

    Set ShellApp = Nothing

    End Function

    Public Function FileFolderExists(strFullPath As String) As Boolean
    'Author : Ken Puls (www.excelguru.ca)
    'Macro Purpose: Check if a file or folder exists
    On Error GoTo EarlyExit
    If Not Dir(strFullPath, vbDirectory) = vbNullString Then FileFolderExists = True

    EarlyExit:
    On Error GoTo 0
    End Function

  43. Brian

    I'm looking to modify your Outlook VBA Code. I have about 10 reports from 10 different storres that get sent to me every day (outlook has a rule to place these reports into a SubFolder called "Data Extract". Each email attachment report is set up like this: ABCDE (<-- StoreID,)_Report1.xls.

    What I need is for the VBA Code (in outlook) to create a new folder on my hard drive (c:\StoreID\) for each Store ID, with the first 5 letters of the attachment (ABCDE) and then all of the attachments with the same first 5 letters of the StoreID, to be saved into their new corresponding folder on my hard drive.

    Is this possible? Or would anyone be able to help?

    1. 1h249s8

      This seems possible, but to help you I would need some more info:
      - you only want to save the attachment, and not the e-mail?
      - every time it saves an attchement, if the folder c:\storeID has not been created yet it has to create a new one?
      - All attachments with an existing storeID folder should be saved in this existing folder?

      Best regards

      Eric

  44. Patrick van Berkel

    Amazing!!! Thank you very much for this code. I am a novice in VBA, so unfortunately it's mostly copy-pasting for me and I still have some question for you that you can hopefully help me with. However, I will try to read the code and the comments first in order to try to at least understand it before I ask silly questions :)

    Kind regards,
    Patrick

  45. Patrick van Berkel

    I've tried to understand the code (as I mentioned above, I'm quite new at this), but it's still over my head. I've tried to add a line which I was hoping would delete the email that was just saved by adding a line (the middle line of the three below:
    ----extract from code----
    mItem.SaveAs StrFile, 3
    mItem.Delete 'added to try to delete the file we just saved
    Next j
    -----end of extract----
    I've tested it by creating a subfolder and copying like 10 mails in there. It does create an output of having saved some of the files in the folder (but not all) and seems to have deleted those respective files. I'm puzzled to why to all were processed though in the same folder.

    Another question that I have is the following:
    If I wanted to only save and delete the mails which were received prior to a certain date (in all folders and sub-folders, would you have any suggestion to how I would do that?

    Thanks in advance for your help and someday I hope to become as good as you in this as I can see so many benefits.

    Kind regards,
    Patrick

    1. Patrick van Berkel

      In the test I have just done there are 7 mails in my test subfolder. If I don't run the code without the mItem.Delete line, then it saves all 7 into the folder I've created, however, if I run the code with the line, it only saves 4 mails into the folder I've created which it also deletes from outlook, however 3 remain in the outlook folder.

      could someone help me to understand what I'm doing wrong? Thank you!

  46. Patrick van Berkel

    I meant to say; I'm puzzled to why not all mails were processed though in the same folder :).

  47. Patrick van Berkel

    I've managed to fix (this site and googling works wonders) to sort out the 2nd question I had, which leaves 'only' two questions:
    1. How do I create a 'date selection' pop-up or something like that where the user can select a date in the calendar, where mails which were prior to the selected date will be saved to the hdd (from each folder and subfolder, and
    2. how can I delete each file that I've just saved to the hdd.

    Your help is very much appreciated. :)

  48. Patrick van Berkel

    Thanks a lot for that. I understand it a bit better now :). I also figured that the "End If" after the date checking should move all the way down till after the deletion, because otherwise it would still delete all the mails, but only save the ones prior to the date enter.

    Hope you don't mind I have some additional questions:
    1. When having a pop-up box where user is requested to enter a date, does the format always need to have the 'US'-format? Or can basically any format be used. In China my colleagues use the yyyy/mm/dd format while we in the Netherlands use dd/mm/yyyy.
    2. in my folder I had other things stored and not only mails (I also had like meeting invitations), which it didn't touch. Now, that's not really a big deal, but from an understanding point of view, could all 'types' be saved and consequently be deleted?

    Again, let me also take the time to thank you again for sharing the code and tweaking it to serve my needs. I see a lot of benefits and potential use. I will share the tool that I will create with this and will obviously mention the source. We've been having a lot of issues with backing up, which even the IT-department wasn't able to come up with a solution, so it's very much appreciated. I just wish that writing code and understanding all the inns and outs would be a skill that I had already acquired in a similar manner as you apparently have. That would make my life and work so much easier :). So, thanks again!!

  49. Patrick van Berkel

    At the risk of seeming impatient, I left a reply 6 days ago, which still seems to be 'stuck' as it is indicated that it is 'awaiting moderation'. Is there anything wrong with the message I sent?

  50. Patrick van Berkel

    I can imagine. Sorry, I was a bit greedy :). Take your time. I was just worried that it ended up in limbo, but now I know. Thanks for your feedback, I wasn't trying to rush you :). I'm literally in a VBA course now, trying to 'bridge the gap' atleast a little bit. Have a great day. Thank you for looking at my questions when convenient :)

Leave a Reply

If the Post Comment button disappears, press your Tab key.

This site uses XenWord.