Outlook 2016 VBA to save attachments from folder to specific loction

只愿长相守 提交于 2019-12-13 21:07:58

问题


I have found this code online and trying to change it to fit my purpose. All I need is to save attachments that go from test@noreplay.com to my Test folder (it is sub folder to Inbox). I do not know how to change it and need your help!

Public Sub saveAttachtoDisk(itm As Outlook.MailItem)
Dim objAtt As Outlook.Attachment
Dim saveFolder As String
saveFolder = "C:\Attachments"
    For Each objAtt In itm.Attachments
        objAtt.SaveAsFile saveFolder & "\" & objAtt.DisplayName
        Set objAtt = Nothing
    Next
End Sub

My VBA look like that (hope it helps).

Again, trying to save attachments from Outlook to a particular folder, from specific sender under the same name (so the new attachment will overwrite the existing one).


回答1:


I have given you a lot to read and do. Work through it all slowly and come back with questions if necessary.

Your comments suggest you have very limited knowledge of Outlook VBA so I am going to start with a brief introduction. I apologise if your knowledge is better than I suspect but I would rather insult your knowledge that leave you utterly confused by my code.

Brief introduction to Outlook VBA

Native VBA is a fairly limited language. It comes with a number of statements and some standard functions but most of its power comes from libraries. Open a VBA Editor, click Tools and then References and you will be shown a long list of libraries. Some at the top of the list will already be ticked. You can scroll down the list and tick others if you need the functionality they provide. It is the “Microsoft Outlook nn.n Object Library” that tells the compiler about folders, mail items and tasks. It is the “Microsoft Excel nn.n Object Library” that tells the compiler about workbooks, worksheets and ranges. (Note, nn.n depends on the version of Office you are using.) So, to program in Outlook VBA, you need to know VBA (which will be the same for Excel, Word and PowerPoint) and the Outlook Object Model.

Outlook holds all your emails, calendar items, tasks and so on in files it calls stores. You may see these files referred to as “PST files” because most have an extension of “PST”. However, an Outlook file with an extension of “OST” is also a store. You may see them referred to as “Accounts” because, by default, Outlook have one store per email account. However, you can have stores that are not linked to an account; for example, my installation has a store named “Archive” and another named “Outlook Data File” neither of which are accounts.

If you look at your folder pane, which is probably down the left side of your Outlook window, you might see something like:

DoeJA@Gmail.com
   :    :    :    :
   Inbox
   :    :    :    :
  Sent Items
   :    :    :    :
JohnDoe@HotMail.com
   :    :    :    :
   Inbox
   :    :    :    :
  Sent Items
   :    :    :    :
Outlook Data File
   :    :    :    :
   Inbox
   :    :    :    :
  Sent Items
   :    :    :    :

“DoeJA@Gmail.com”, “JohnDoe@HotMail.com” and “Outlook Data File” are the names of stores. The file names are probably “DoeJA@Gmail.com.OST”, “JohnDoe@HotMail.com.PST” and “Outlook Data File.PST”. However, a VBA programmer is not interested in the filenames; the names that appear in the folder pane are the names that matters.

An irritating feature of VBA is that there is usually more than one way of achieving the same effect. Consider:

Option Explicit
Sub DsplSingleEmail1()

  Dim NS As NameSpace
  Dim FldrSrc As Folder

  Set NS = Application.GetNamespace("MAPI")
  Set FldrSrc = NS.Folders("DoeJA@Gmail.com").Folders("Inbox")

  With FldrSrc.Items(1)
    Debug.Print .ReceivedTime & " " & .Subject
  End With

End Sub
Sub DsplSingleEmail 2()

  Dim FldrSrc As Folder

  Set FldrSrc = Session.Folders("DoeJA@Gmail.com").Folders("Inbox")

  With FldrSrc.Items(1)
    Debug.Print .ReceivedTime & " " & .Subject
  End With

End Sub
Sub DsplSingleEmail 3()

  Dim FldrSrc As Folder

  Set FldrSrc = CreateObject("Outlook.Application"). _
                        GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)

  With FldrSrc.Items(1)
    Debug.Print .ReceivedTime & " " & .Subject
  End With

End Sub

All three macros, output the date and time received and the subject of what is almost certainly the oldest email in Inbox. If you want to try them on your system, you will have to replace “DoeJA@Gmail.com” with the name of a store on your system that has an active Inbox.

In macro DsplSingleEmail1, I have used method GetNamespace. In macro DsplSingleEmail2, I have used method Session. The documentation says these two approaches are equivalent but I have not found an explanation of why there are two equivalent methods. This does not matter if you write your own code; pick whichever approach you prefer. But if you intend to look for snippets of code online, you must be prepared for programmer A using one approach and programmer B using another. When you merge snippets that use different approaches you will have to understand all the approaches well enough to adjust the code to use your favourite.

Both GetNamespace and Session are methods of Application. In one macro, I have told the compiler this while in the other I have left the compiler to work this out for itself.

In macro DsplSingleEmail3, I have used method Namespace but have started my statement with CreateObject("Outlook.Application"). This would have been necessary if I was running this code from Excel but is not necessary here because I am already within Outlook. I have also used method GetDefaultFolder with parameter olFolderInbox. Outlook defaults to having all its standard folders within store “Outlook Data File”. But, on my system, the installation wizard has arranged for my emails to be imported to two other stores. No doubt somewhere there is the functionality to change the defaults but I have never bothered to look because I do not know which store’s Inbox I would make the default. The only reason, DsplSingleEmail3 works on my system is because I have copied some junk emails to the Inbox within “Outlook Data File”. I include Debug.Print FldrSrc.Parent.Name because it outputs the name of the store containing the default Inbox.

The purpose of the above is to demonstrate some of the problems with looking for code snippets without have the background to understand the context. It appears the author of the snippet you found assumed readers would understand how to use it. I learnt Excel VBA from a book. I visited a large library and borrowed all their Excel VBA primers. At home I tried them all and then bought the one that matched my learning style. I learnt Outlook VBA by experimentation since I was unable to find a good primer for Outlook VBA. I doubt it is possible to learn either Excel or Outlook VBA by looking for useful bits of code online. If you want to write macros to help your employer, you must arrange some relief from your normal workload so you have the uninterrupted time to learn VBA and the Outlook Object model. This relief will quickly repay your employer because you will be able to write macros that save hours of effort by your colleagues and yourself.

Addressing your requirement

I find that writing macros to process emails divide into two parts. Part 1 is deciding how best to select the emails to be processed. Part 2 is deciding how to achieve whatever effect you wish to achieve by processing the selected emails. I will describe four distinct methods of selecting emails and show how to use each of those methods. However, I will provide only one macro for processing selected emails. Each of the four selection methods will call the same processing macro. This demonstrates there really are two parts and will show you how the different selection methods work and decide which is the most appropriate for your current and future requirements.

My processing macro is a little more complicated than yours. Your macro is designed specifically to work with a rule. My macro will work with user selection, scanning a folder, a new item event and a rule.

My understanding of your requirement is: Emails from “test@noreplay.com” have attachments. The attachments are to be saved to “C:\Attachments” overwriting any previous attachments with the same DisplayName. Processed emails are to be moved to folder “Test” under the Inbox which received the email. You have used a rule to move the emails but do not know how to save the attachments using a rule.

The first part of my processing macro is similar to yours although I have made a few changes. Since an Outlook macro must work with Outlook folders, I reserve the word “folder” for Outlook. I use “path” when I need to refer to disc folders. I do not write Outlook.MailItem or Outlook.Attachment when MailItem or Attachment will do. You would need the prefix “Outlook” if the compiler did not know which library defines MailItem and Attachment; which does not apply here. I do not know what store contains the Inbox you wish to examine so have called it "DoeJA@Gmail.com". Replace this name with the name of your store. I am a keen user of the With statement and have included two.

Be warned that there are different types of attachment. I have never seen some types so do not know if they have a DisplayName but it is likely that your emails only have the most common type of attachment. If you have a problem saving attachments, describe the problem and I will suggest how to avoid that problem. SaveAsFile overwrites any existing file with the same name without warning. This is not a problem since this matches your requirement.

You use a rule to move the email to the required folder but only one of my selection macros uses a rule so the processing macro must move the email if necessary. If the email is already in folder “Test” within folder “Inbox” then it does not need to be moved.

All mail items have the folder that contains them as a parent. A folder is either within another folder or it is within a store. The containing folder or store is the folder’s parent. A store does not have a parent. Within my processing macro, you will find:

With ItemCrnt
  If .Parent.Name = "Test" And .Parent.Parent.Name = "Inbox" Then
    ' MailItem is already in destination folder
  Else
    .Move FldrDest
  End If
End With

ItemCrnt.Parent.Name is the name of the folder containing the mail item. If the mail item is in the required folder, this name will be “Test”. ItemCrnt.Parent.Parent.Name is the name of the folder containing the folder containing the mail item. If the mail item is in the required folder, this name will be “Inbox”. So this code will move the mail item to the destination folder if it is not already in the destination folder. Notice how I can string properties together with dots. Be careful with this feature. A store does not have a parent so if ItemCrnt is in folder “Test”, ItemCrnt.Parent.Parent.Parent.Name will give an error.

The first selection macro, SelectEmailsUser, requires the user select one or more emails before running the processing macro. I have never used this approach in a live installation but find it invaluable during development. With this approach, I can start with one email that is easy to process. As I test the processing macro, I can slowly introduce more and more complicated emails and several emails in a single run. No other approach offers the same control over the sequence in which emails are presented to the macro.

The second selection macro, SelectEmailsScan, uses the approach I use most often. With this approach, I read up or down a folder examining the properties of each email and deciding which I wish to process.

You wish to move the processed emails which introduces a complication if you use a simple For-Loop. A folder is an example of a collection. With a collection, you normally access its members by position: 1, 2, 3, 4 and so on. If you move mail item 5 to another folder, you are deleting that mail item from this folder and adding it to the other. When you delete mail item 5, mail item 6 becomes mail item 5, mail item 7 becomes mail item 6 and so on. If your For-Loop now examines mail item 6, you are examining the old mail item 7 and are ignoring the old mail item 6. There are a number of work-arounds but the easiest is to examine the emails in reverse order: 1000, 999, 998, and so on. Now if you delete email 998, you do not mind that emails 1000 and 999 change position because you have already examined them.

The third selection macro, InboxItems_ItemAdd uses a new item event. You can ask Outlook to run a macro every time something happens. My code asks Outlook to run a macro when a new email is added to folder Inbox. This macro calls my processing macro if the email was sent by a particular sender. This matches your rule except that my macro both moves the email to folder “Test” and saves the attachments.

The fourth selection method involves linking a “script” to a rule. This rule must select emails sent by “test@noreplay.com”. Optionally, this rule can move selected emails to folder “Test”. If it does not, my processing macro will move it. The rule option is “Run a script” which is confusing. There are several scripting languages including VBscript. The “script” cannot use any of these scripting languages; it must be an Outlook VBA macro.

The rule option “Run a script” is also confusing because of the conflicting information. Some sites say Microsoft have disabled it and provide complicated instructions on how to un-disable it. Other sites do not mention any such problem. Rule option “Run a script” works on my system so I can only hope it works on yours. If it does not work, you will have to choose one of the other approaches.

Installation and Testing

I have simulated your system as far as I can. I have two email addresses which I will call Address1 and Address2. Address1 is my main address which is known to my family, friends and chosen suppliers. Address2 is the one which I publish openly and which I will discard if it is picked up by too many scammers.

I have created an Outlook folder “Test” under Address1’s “Inbox”. I have created a disc folder “C:\Attachments”. I monitor for emails arriving at Address1 from Address2. You will have to change my store names but otherwise my macros should work unchanged on your system.

Please delete your existing rule. I need you to delete your rule because (1) it will interfere with selection methods 1 to 3 and (2) I cannot discover how to add a script to an existing rule. Please delete your existing code which you will not need.

Your image shows that you have placed your code in Module1. As you start new projects, you may add Module2, Module3 and so on. It soon becomes a pain to find the module containing the code you want to look at today. You have the Properties window open. (F4 opens the Property window if it is now closed.) The only property of a module is its name which you can change from the default ModuleNN. I suggest you rename “Module1” as “ModXxxxx” where “Xxxxx” is a name that means something to you. The “Mod” is not essential but I find it helpful. If you have macro “Xxxxx” in module “Xxxxx” you cannot access that macro. By naming all my modules with a prefix of “Mod”, I avoid this problem.

Have Option Explicit at the top of every module. Look up this statement to learn about the benefits it offers. Look up any statement in my code you do not recognise. Come back with questions if necessary but the more you can research for yourself, the faster you will develop.

If you want to try macros DsplSingleEmail1 to DsplSingleEmail3, you can copy and paste from this answer to your module. You may need to copy some junk emails to store “Outlook Data File” if you want to try Experiment3.

Selection Method 1

Copy the following code to your module:

Public Sub SaveAttachAndMoveEmail(ByRef ItemCrnt As MailItem)

  Dim Attach As Attachment
  Dim FldrDest As Folder
  Dim PathSave As String

  PathSave = "C:\Attachments"
  Set FldrDest = Session.Folders("Address1").Folders("Inbox").Folders("Test")

  With ItemCrnt
    For Each Attach In .Attachments
      With Attach
        .SaveAsFile PathSave & "\" & .DisplayName
      End With
    Next

    If .Parent.Name = "Test" And .Parent.Parent.Name = "Inbox" Then
      ' MailItem is already in destination folder
    Else
      .Move FldrDest
    End If

  End With


End Sub
Sub SelectEmailsUser()

  Dim Exp As Explorer
  Dim ItemCrnt As MailItem

  Set Exp = Outlook.Application.ActiveExplorer

  If Exp.Selection.Count = 0 Then
    Call MsgBox("Pleaase select one or more emails then try again", vbOKOnly)
    Exit Sub
  Else
    For Each ItemCrnt In Exp.Selection
      With ItemCrnt
        Debug.Print .ReceivedTime & "|" & .Subject & "|" & .SenderEmailAddress
      End With
      Call SaveAttachAndMoveEmail(ItemCrnt)
    Next
  End If

End Sub 

Macro SaveAttachAndMoveEmail is my processing macro which I have explained above. Replace “Address1” with the name of the store containing the Inbox you wish to monitor. Macro SelectEmailsUser uses ActiveExplorer to access emails selected by the user. The macro outputs a few properties of each email to the Immediate Window. To test the macro, I selected a number of emails with and without attachments and within and outside folder “Test”. I assume your emails are all in “Test”. Why not move some back to Inbox, select then and run macro SelectEmailsUser.

Selection Method 2

Add the following code to your module:

Sub SelectEmailsScan()

  Dim FldrSrc As Folder
  Dim InxItemCrnt As Long

  Set FldrSrc = Session.Folders("myemail@gmail.com").Folders("Inbox")

  For InxItemCrnt = FldrSrc.Items.Count To 1 Step -1
    With FldrSrc.Items.Item(InxItemCrnt)
      If .Class = olMail Then
        If .SenderEmailAddress = "myemail@gmail.com" Then
          Call SaveAttachAndMoveEmail(FldrSrc.Items.Item(InxItemCrnt))
        End If
      End If
    End With
  Next

End Sub

Replace “Address1” with the name of the store containing the Inbox you wish to monitor.

I tested this code (and later code) by sending emails from my second account to my first. You could test this code by moving previous emails sent by “test@noreplay.com” from folder “Test” to folder “Inbox”. As I have said, this is the approach I use the most. Having Outlook monitor for interesting emails seems easier but I find the control of being able to run a macro when I wish more in tune with how I run my life.

Selection Method 3

Within the VBA Editor, the Project Explorer window is normally down the left hand side. The top line is “Microsoft Outlook Objects”. If there is a “+” against this line, please click the “+” to expand “Microsoft Outlook Objects”. The next line down is “ThisOutlookSession”. Click “ThisOutlookSession” to select it. The code area becomes blank. Previously you were seeing the code within your module. “ThisOutlookSession” is another area in which you can place code. You can place any code here but I reserve it for code that MUST be placed here. Copy the following code into “ThisOutlookSession”:

Option Explicit
Private WithEvents InboxItems As Items
Private Sub Application_Startup()
  Set InboxItems = Session.Folders("Address1").Folders("Inbox").Items
End Sub
Private Sub InboxItems_ItemAdd(ByVal ItemCrnt As Object)
  With ItemCrnt
    If .Class = olMail Then
      If .SenderEmailAddress = "test@noreplay.com" Then
        Call SaveAttachAndMoveEmail(ItemCrnt)
      End If
    End If
  End With
End Sub   

Private WithEvents InboxItems As Items defines the object InboxItems.

Private Sub Application_Startup() … End Sub specifies a subroutine that is to be run when Outlook is started. Having such a subroutine means that Outlook will immediately ask if you want macros enabled. You must answer “Yes” if you want events monitored.

Set InboxItems = Session.Folders("Address1").Folders("Inbox").Items identifies which folder you want monitored. When an item is added to this folder, the subroutine Xxxxx_ItemAdd is executed. Xxxxx is the name of the object you defined Private WithEvents …. You can monitor as many folders as you wish providing you specify a separate WithEvents object for each folder.

The code within macro InboxItems_ItemAdd checks the item is a mail item and was sent by “test@noreplay.com”. If these are true, it calls my processing macro. You will need to exit Outlook (not forgetting to save “VbaProject.OTM”) and then restart it to activate the monitoring of Inbox.

If you are watching Inbox when a mail item arrives from “test@noreplay.com”, you can see it appear briefly and then disappear as it is moved to folder “Test”. I tested event monitoring by sending emails from my second email address. You will have to wait for emails to come from test@noreplay.com.

Selection Method 4

Before setting up method 4, you need to disable method 3. You can delete the code from “ThisOutlookSession” but I place quotes down the left edge to turn all the statements into comments so they are there for reference next time I need to monitor an event. Having disabled method 3, close Outlook and reopen it.

You will need to create a new rule to replace the one I got you to delete. The rule I created to test this approach selected emails from my Address2. It did not move these emails to “Test” because it my processing macro does that. My rule included running a script which yours did not. My steps in creating this rule were:

  • Move one of the target emails from “Test” to “Inbox”.
  • Select that target email.
  • On Home tab, click “Rules”.
  • Click “Create Rule”. A “Create Rule” window appears.
  • Tick “From test@noreplay.com”.
  • Check nothing else is ticked.
  • Click “Advanced”. A “Rule Wizard” window appears.
  • Check “From test@noreplay.com” is ticked but nothing else is ticked.
  • Click “Next”. Another “Rule Wizard” window appears.
  • Click “Run a script” which is near the bottom of the list.
  • Check nothing else is ticked.
  • Click “a script” in the Step 2 box at the bottom. If you have not done so before in this run of Outlook, you will be asked to enable macros. A “Select script” window appears.
  • Select “Project1.SaveAttachAndMoveEmail” and click “OK”. Note 1: the macro name will probably be truncated. Note 2: This choice is probably already selected and is your only choice.
  • Click “Next”. Another “Rule Wizard” window appears.
  • Check nothing is ticked.
  • Click “Next”. The final “Rule Wizard” window appears.
  • Enter a different name for the rule if you do not like Outlook’s default name.
  • You may wish to tick “Run this rule now on messages already in Inbox” to remove the email you moved from “Test”.
  • Click “Finish”. If you ticked “Run this rule now on messages already in Inbox” you will see a progress window while the rule is run.

I cannot find any good documentation on the “Run a script” option. My experimentation suggests a macro listed on the “Select script” window must be Public and must have a parameter list that conforms to the rules for such macros. I have read that such macros can have four parameters. The first parameter is compulsory but the others can be omitted. The first parameter must be “ByRef itm As MailItem” or some reasonable variation.



来源:https://stackoverflow.com/questions/52782539/outlook-2016-vba-to-save-attachments-from-folder-to-specific-loction

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!