Get the folder where the last mailitem was moved in Outlook? - vba

I have a vbscript macro that I'm using in Outlook. It moves a mailitem to some folder, say X. After I run the macro and I try to manually move a mailitem from Outlook with Control-v, it defaults to folder X. I would like Control-v to default to the folder that it would have used before I ran the macro.
Is there some way in VBScript to find out what folder the last mailitem was move to, and to return that to be the default folder after I run my script? Or is there a way to move a mailitem in my script without the destination folder being remembered by Outlook Control-v after I run the script?
Thanks for any hints.
OK, here is the code I'm using. It is a macro to save a mailitem as HTML and open it in a browser. I save any attachments in a separate directory and I add in a list of URLs to the attachments. I do this by modifying the mailitem, but I don't want change the original message - I want it to remain in my inbox as it was. So I create a copy and when I'm done I want to get rid of the copy. For some reason the .Delete method just doesn't do anything. So, one solution for me would be to figure out why .Delete is not working. I created a work-around by just moving the copied message into my deleted items folder. The problem I have with this is that I often use control-v to move items from my inbox to an archive folder. Once I run the macro, though, the default folder for control-v is the deleted item folder. I keep archiving items there by mistake. So the best solution would be to get .Delete working, but even then, that might change the control-v default behavior after running the macro.
Here's the code. I've only been doing vba for a couple of days, so any tips on things I'm missing appreciated.
Option Explicit
Sub CreateHTML()
Select Case TypeName(Outlook.Application.ActiveWindow)
Case "Inspector"
CreateHTMLfromObject Outlook.Application.ActiveInspector.CurrentItem
Case "Explorer"
Dim objItem As Object
For Each objItem In Outlook.Application.ActiveExplorer.Selection
CreateHTMLfromObject objItem
Next
End Select
End Sub
Sub CreateHTMLfromObject(objItem As Object)
' For now, assume all items are mail items
'Select Case objItem.Class
'Case olMail
Dim objMailOrig As MailItem
Dim objMailCopy As MailItem ' Work on a copy of the message
Set objMailOrig = objItem
Set objMailCopy = objMailOrig.copy
' Where all HTML versions of messages will be stored
Dim fileDir As String
fileDir = "C:\Lib\olHTML\"
' A unique message id from the original message
Dim MsgId As String
MsgId = objMailOrig.EntryID
' The file the HTML version of the message will be stored in
Dim fileName As String
fileName = MsgId & ".html"
' The full file system path where the HTML verison of the message will be stored
Dim filePath As String
filePath = fileDir & fileName
' ---------------------------------------------------------------
' Save Attachments
' ---------------------------------------------------------------
' Subdirectory for attachments on this message
' A unique subdirectory for each message
Dim atmtDir As String
atmtDir = MsgId & "_atmt\"
' Full file system path to the attachment directory
Dim atmtDirPath As String
atmtDirPath = fileDir & atmtDir
' File system object for creating the attachment folder
Dim oFSO
Set oFSO = CreateObject("Scripting.FileSystemObject")
If (objMailCopy.Attachments.Count > 0) And (Not oFSO.FolderExists(atmtDirPath)) Then
oFSO.CreateFolder (atmtDirPath)
End If
' To hold the full file system path to each attachment file
Dim atmtFilePath As String
' String to accumulate HTML code for displaying links to attachments
' in the body of the HTML message
Dim atmtLinks As String
atmtLinks = " "
Dim atmt As Attachment
For Each atmt In objMailCopy.Attachments
atmtFilePath = atmtDirPath & atmt.fileName
atmt.SaveAsFile atmtFilePath
' create a relative URL
atmtLinks = atmtLinks & _
"<br><a href='" & atmtDir & atmt.fileName & "'>" & atmt.fileName & "</a>"
Next atmt
' ---------------------------------------------------------------
' Add links to attachments
' ---------------------------------------------------------------
' This changes the original message in Outlook - so we work on a copy
' Convert body to HTML if RTF, Text or other format
If (objMailCopy.BodyFormat = olFormatPlain Or olFormatRichText Or olFormatUnspecified) Then
objMailCopy.BodyFormat = olFormatHTML
End If
' Add attachments links at the beginning
If objMailCopy.Attachments.Count > 0 Then
objMailCopy.HTMLBody = _
"<p>" & "Attachments: " & atmtLinks & "</p>" & objMailCopy.HTMLBody
End If
' ---------------------------------------------------------------
' Save the HTML message file
' ---------------------------------------------------------------
objMailCopy.SaveAs filePath, olHTML
' ---------------------------------------------------------------
' Delete the copy from Outlook
' ---------------------------------------------------------------
'! This seems to have no effect
' objMailCopy.Delete
' Move copied message to deleted items folder
objMailCopy.Move Outlook.Application.GetNamespace("MAPI").GetDefaultFolder(olFolderDeletedItems)
' ---------------------------------------------------------------
' Open the HTML file with default browser
' ---------------------------------------------------------------
Dim url As String
url = "file:///" & filePath
CreateObject("WScript.Shell").Run (url)
End Sub

i would not make a copy in the inbox and delete that afterwards (that will make your deleted-folder explode one day), but make your changes in the local copy of the message-file:
here an example:
Sub changelocalcopy(olitem As Outlook.MailItem)
Dim oNamespace As Outlook.NameSpace
Set oNamespace = Application.GetNamespace("MAPI")
Dim oSharedItem As Outlook.MailItem
Dim pfaddatei As String
pfaddatei = c:\test.msg 'path for your local copy here
olitem.SaveAsFile pfaddatei
Set oSharedItem = oNamespace.OpenSharedItem(pfaddatei)
'now do your changes
'you will not want the following line, I leave it here in case you Need it:
Kill pfaddatei
oSharedItem.Close (olDiscard)
Set oSharedItem = Nothing
Set oNamespace = Nothing
End Sub

Related

Saving attachments results in memory errors

I need to search through 9,000 emails and save the attachments with a certain timestamp (these are security camera feeds).
The code works on a small number of emails, but after about 20 the processing in Outlook appears to speed up significantly (attachments stop saving) and then Outlook hangs with a memory error.
My guess is the save step is not completed before the script moves to the next email in the target folder and therefore the backlog of saves becomes too large for Outlook.
' this function grabs the timestamp from the email body
' to use as the file rename on save in the following public sub
Private Function GetName(olItem As MailItem) As String
Const strFind As String = "Exact Submission Timestamp: "
Dim olInsp As Inspector
Dim wdDoc As Object
Dim oRng As Object
Dim strDate As String
With olItem
Set olInsp = .GetInspector
Set wdDoc = olInsp.WordEditor
Set oRng = wdDoc.Range
With oRng.Find
Do While .Execute(strFind)
oRng.Collapse 0
oRng.End = oRng.End + 23
strDate = oRng.Text
strDate = Replace(strDate, Chr(58), Chr(95))
GetName = strDate & ".jpg"
Exit Do
Loop
End With
End With
lbl_Exit:
Set olInsp = Nothing
Set wdDoc = Nothing
Set oRng = Nothing
Exit Function
End Function
Public Sub SaveAttachmentsToDisk24(MItem As outlook.MailItem)
Dim oAttachment As outlook.Attachment
Dim sSaveFolder As String
Dim strFname As String
sSaveFolder = "C:\Users\xxxxx\"
For Each oAttachment In MItem.Attachments
If oAttachment.FileName Like "*.jpg" Then
strFname = GetName(MItem)
oAttachment.SaveAsFile sSaveFolder & strFname
Set oAttachment = Nothing
Set MItem = Nothing
End If
Next oAttachment
There are other possibilities but my belief is that the memory errors are the result of creating Word objects and then not closing them. Om3r asked for more information but you ignored his requests making it impossible to provide a definitive answer. However, I wanted to prove it was possible to extract attachments from a large number of emails without problems so I have made some guesses.
I understand why you need a routine that will scan your Inbox for the backlog of 8,000 camera feed emails. I do not understand why you want to use an event to monitor your Inbox as well. I cannot believe this is a time critical task. Why not just run the scan once or twice a day? However, the routine I have coded could be adapted to create a macro to be called by an event routine. My current code relies of global variables which you will have to change to local variables. I am not a fan of global variables but I did not want to create a folder reference for every call of the inner routine and the parameter list for a macro that might be called by an event routine is fixed.
To test the code I planned to create, I first generated 790 emails to myself that matched (I hope) your camera feed emails. I had planned to create more but I think my ISP has classified me as a spammer, or perhaps a flamer, and it would not let me send any more. The body of these emails looked like:
xxx Preamble xxx ‹cr›‹lf›|
Exact Submission Timestamp: 2019-02-22 15:00:00 ‹cr›‹lf›|
xxx Postamble xxx ‹cr›‹lf›|
Your code requires the string “Exact Submission Timestamp:” followed by a date which you use as a file name. I have assumed that date in in a format that VBA can recognise as a date and I have assumed the date is ended by a standard Windows newline (carriage return, line feed). The second assumption would be easy to change. I have a routine that will accept many more date formats than VBA’s CDate which I can provide if necessary.
Each email has a different date and time between November, 2018 and February, 2019.
I would never save 8,000 files in a single disc folder. Even with a few hundred files in a folder, it becomes difficult to find the one you want. My root folder is “C:\DataArea\Test” but you can easily change that. Given the timestamp in my example email, my routine would check for folder “C:\DataArea\Test\2019” then “C:\DataArea\Test\2019\02” and finally “C:\DataArea\Test\2019\02\22”. If a folder did not exist, it would be created. The attachment is then saved in the inner folder. My code could easily be adapted to save files at the month level or the hour level depending on how many of these files you get per month, day or hour.
My routine checks every email in Inbox for the string “Exact Submission Timestamp:” followed by a date. If it finds those, it checks for an attachment with an extension of JPG. If the email passes all these tests, the attachment is saved in the appropriate disc folder and the email is moved from Outlook folder “Inbox” to “CameraFeeds1”. The reasons for moving the email are: (1) it clears the Inbox and (2) you can rerun the routine as often as you wish without finding an already processed email. I named the destination folder “CameraFeeds1” because you wrote that you wanted to do some more work on these emails. I thought you could move the emails to folder “CameraFeeds2” once you had completed this further work.
I assumed processing 790 or 8,000 emails would take a long time. In my test, the duration was not as bad as I expected; 790 emails took about one and a half minutes. However, I created a user form to show progress. I cannot include the form in my answer so you will have to create your own. Mine looks like:
The appearance is not important. What is important is the name of the form and the four controls on the form:
Form name: frmSaveCameraFeeds
TextBox name: txtCountCrnt
TextBox name: txtCountMax
CommandButton name: cmdStart
CommandButton name: cmdStop
If you run the macro StartSaveCameraFeeds it will load this form. Click [Start] to start the save process. You can let the macro run until it has checked every email in the Inbox or you can click [Stop] at any time. The stop button is not as important as I feared. I thought the routine might take hours but that was not the case.
You don’t report where your 8,000 emails are. I have an Inbox per account plus the default Inbox which I only use for testing. I moved the 790 test emails to the default Inbox and used GetDefaultFolder to reference it. I assume you know how to reference another folder if necessary. Note I use Session instead of a name space. These two methods are supposed to be equivalent but I always use Session because it is simpler and because I once had a failure with a name space that I could not diagnose. I reference folder “CameraFeeds1” relative to the Inbox.
You will have to adjust my code at least partially. For the minimum changes, do the following:
Create a new module and copy this code into it:
Option Explicit
Public Const Marker As String = "Exact Submission Timestamp: "
Public Const RootSave As String = "C:\DataArea\Test"
Public FldrIn As Outlook.Folder
Public FldrOut As Outlook.Folder
Sub StartSaveCameraFeeds()
' Reference outlook folders then pass control to frmSaveCameraFeeds
Set FldrIn = Session.GetDefaultFolder(olFolderInbox)
Set FldrOut = FldrIn.Parent.Folders("CameraFeeds1")
Load frmSaveCameraFeeds
With frmSaveCameraFeeds
.Caption = "Saving jpg files from Camera feed emails"
.txtCountCrnt = 0
.txtCountMax = FldrIn.Items.Count
.Show vbModal
End With
' Form unloaded by cmdStop within form
Set FldrIn = Nothing
Set FldrOut = Nothing
End Sub
Public Sub SaveCameraFeed(ByRef ItemCrnt As MailItem)
' Checks a single mail item to be a "camera feed" email. If the mail item is
' a "camera feed" email, it saves the JPG file using the date within the
' email body as the file name. If the mail item is not a "camera feed"
' email, it does nothing.
' To be a camera feed mail item:
' * The text body must contain a string of the form: "xxxyyyy" & vbCr & vbLf
' where "xxx" matches the public constant Marker and "yyy" is recognised
' by VBA as a date
' * It must have an attachment with an extension of "JPG" or "jpg".
' If the mail item is a camera feed email:
' * In "yyy" any colons are replaced by understores.
' * The JPG attachment is saved with the name yyy & ".jpg"
Dim DateCrnt As Date
Dim DateStr As String
Dim DayCrnt As String
Dim InxA As Long
Dim MonthCrnt As String
Dim PathFileName As String
Dim PosEnd As Long
Dim PosStart As Long
Dim SomethingToSave As Boolean
Dim YearCrnt As String
SomethingToSave = False ' Assume no JPG to save until find otherwise
With ItemCrnt
PosStart = InStr(1, .Body, Marker)
If PosStart > 0 Then
PosStart = PosStart + Len(Marker)
PosEnd = InStr(PosStart, .Body, vbCr & vbLf)
DateStr = Mid$(.Body, PosStart, PosEnd - PosStart)
If IsDate(DateStr) Then
DateCrnt = DateStr
For InxA = 1 To .Attachments.Count
If LCase(Right$(.Attachments(InxA).Filename, 4)) = ".jpg" Then
SomethingToSave = True
Exit For
End If
Next
End If
End If
If SomethingToSave Then
DateStr = Replace(DateStr, ":", "_")
YearCrnt = Year(DateCrnt)
MonthCrnt = Month(DateCrnt)
DayCrnt = Day(DateCrnt)
Call CreateDiscFldrIfItDoesntExist(RootSave, YearCrnt, MonthCrnt, DayCrnt)
PathFileName = RootSave & "\" & YearCrnt & "\" & MonthCrnt & "\" & DayCrnt & _
"\" & Trim(DateStr) & ".jpg"
.Attachments(InxA).SaveAsFile PathFileName
.Move FldrOut
End If
End With
End Sub
Public Sub CreateDiscFldrIfItDoesntExist(ByVal Root As String, _
ParamArray SubFldrs() As Variant)
' If a specified disk folder (not an Outlook folder) does not exist, create it.
' Root A disk folder which must exist and for which the user
' must have write permission.
' SubFldrs A list of sub-folders required within folder Root.
' Example call: CreateDiscFldrsIfNecessary("C:\DataArea", "Aaa", "Bbb", "Ccc")
' Result: Folder "C:\DataArea\Aaa\Bbb\Ccc" will be created if it does not already exist.
' Note: MkDir("C:\DataArea\Aaa\Bbb\Ccc") fails unless folder "C:\DataArea\Aaa\Bbb" exists.
Dim Filename As String
Dim Fldrname As String
Dim InxSF As Long
Fldrname = Root
For InxSF = LBound(SubFldrs) To UBound(SubFldrs)
Fldrname = Fldrname & "\" & SubFldrs(InxSF)
If Not PathExists(Fldrname) Then
Call MkDir(Fldrname)
End If
Next
End Sub
Public Function PathExists(ByVal Pathname As String) As Boolean
' Returns True if path exists
' Coded by Tony Dallimore
' Based on code written by iDevlop: http://stackoverflow.com/a/28237845/973283
On Error Resume Next
PathExists = ((GetAttr(Pathname) And vbDirectory) = vbDirectory)
On Error GoTo 0
End Function
I must warn you that I have modules full of standard routines that I use all the time. I believe I have included all the standard routines used by the code I have written for you. If the code fails because a sub or function is missing, post a comment and I will apologise and add the missing macro to my code.
Near the top of the above code is Public Const RootSave As String = "C:\DataArea\Test". You will have to change this to reference your root folder.
The first statement of Sub StartSaveCameraFeeds() is Set FldrIn = Session.GetDefaultFolder(olFolderInbox). Amend this as necessary if the emails are not in the default Inbox.
In the body of Sub StartSaveCameraFeeds() you will find PosEnd = InStr(PosStart, .Body, vbCr & vbLf). If the date string is not ended by a standard Windows’ newline, amend this statement as necessary.
Create a user form. Add two TextBoxes and two CommandButtons. Name them as defined above. Copy the code below to the code area of the form:
Option Explicit
Private Sub cmdStart_Click()
' Call SaveCameraFeed for every MailItem in FldrIn
Dim CountMax As Long
Dim InxI As Long
Dim MailItemCrnt As MailItem
With FldrIn
CountMax = FldrIn.Items.Count
For InxI = CountMax To 1 Step -1
If .Items(InxI).Class = olMail Then
Set MailItemCrnt = .Items(InxI)
Call SaveCameraFeed(MailItemCrnt)
Set MailItemCrnt = Nothing
End If
txtCountCrnt = CountMax - InxI + 1
DoEvents
Next
End With
Unload Me
End Sub
Private Sub cmdStop_Click()
Unload Me
End Sub
The form code should not need amendment.
As I have already written, this code processed 790 camera feed emails in about one and a half minutes. I coded a further routine that checked that for every email the date matched the name of a jpg file. I could include this routine in my answer if you would like to perform the same check.

How to Create Task in Shared Task List?

I'm trying to convert an email to task and place that task inside a shared task folder. My co-worker shared the folder and has given me owner access to the folder as well.
We have utilized the script on slipstick to accomplish this. This code does work for my co-worker, but does not work for me.
When I dig into the list of folders I see my personal task list as a folder and not the shared folder. (Via the code below)
Is there any way that I can add a task to a shared task folder?
Public strFolders As String
Public Sub GetFolderNames()
Dim olApp As Outlook.Application
Dim olSession As Outlook.NameSpace
Dim olStartFolder As Outlook.MAPIFolder
Dim lCountOfFound As Long
lCountOfFound = 0
Set olApp = New Outlook.Application
Set olSession = olApp.GetNamespace("MAPI")
' Allow the user to pick the folder in which to start the search.
Set olStartFolder = olSession.PickFolder
' Check to make sure user didn't cancel PickFolder dialog.
If Not (olStartFolder Is Nothing) Then
' Start the search process.
ProcessFolder olStartFolder
End If
' Create a new mail message with the folder list inserted
Set ListFolders = Application.CreateItem(olMailItem)
ListFolders.Body = strFolders
ListFolders.Display
' clear the string so you can run it on another folder
strFolders = ""
End Sub
Sub ProcessFolder(CurrentFolder As Outlook.MAPIFolder)
Dim i As Long
Dim olNewFolder As Outlook.MAPIFolder
Dim olTempFolder As Outlook.MAPIFolder
Dim olTempFolderPath As String
' Loop through the items in the current folder.
For i = CurrentFolder.Folders.Count To 1 Step -1
Set olTempFolder = CurrentFolder.Folders(i)
olTempFolderPath = olTempFolder.FolderPath
' Get the count of items in the folder
olCount = olTempFolder.Items.Count
'prints the folder path and name in the VB Editor's Immediate window
Debug.Print olTempFolderPath & " " & olCount
' prints the folder name only
' Debug.Print olTempFolder
' create a string with the folder names.
' use olTempFolder if you want foldernames only
strFolders = strFolders & vbCrLf & olTempFolderPath & " " & olCount
lCountOfFound = lCountOfFound + 1
Next
' Loop through and search each subfolder of the current folder.
For Each olNewFolder In CurrentFolder.Folders
'Don't need to process the Deleted Items folder
If olNewFolder.Name <> "Deleted Items" Then
ProcessFolder olNewFolder
End If
Next
End Sub
In addition the Task folder, you will need permission to your co-worker's Mailbox. (Not Inbox nor other folders.)
If you add the mailbox to your profile, see the accepted answer here.
If you do not add the mailbox to your profile, see the answer describing GetSharedDefaultFolder. Redemption is not required.

VBA, MS Outlook, Folder Item

I want to implement an VBA application, which uses the selected object (E-mail, task, folder).
My try with Application.ActiveExplorer.Selection.Item(i_item) seems to return only mails, tasks, calender entries or notes but never an folder (e.g. 'Inbox\').
When the user selects an e-mail, and then starts the VBA macro, the solution Application.ActiveExplorer.Selection.Item(i_item) delivers the desired results.
However, if the last item picked by the Outlook user was an folder (e.g. 'Sent Mails'). And the VBA makro started afterward, than the macro should recive the Folder Item (without additional user interaction). This is currently not the case. The code above still delivers the e-mail, or task.
How do I check, if the last selection was on an folder (not an e-mail, etc)?
How do I access the Folder item?
If this is not possible I will switch back to Pickfolder (like proposd by Darren Bartrup-Cook) but this is not me prefred solution.
I want to get the selected folder in order to change its icon, so our code is somehow the same.
I noticed that Application.ActiveExplorer.Selection.Item(i_item) it is not perfect, since it throws an exception for empty folders or on calendar etc.
So I use Application.ActiveExplorer.CurrentFolder.DefaultMessageClass (Application.ActiveExplorer.NavigationPane.CurrentModule.Name or Application.ActiveExplorer.NavigationPane.CurrentModule.NavigationModuleType) in order to figure out where I actually am.
By that approach it is easy to get current selected folder
Dim folder As Outlook.MAPIFolder
Dim folderPath As String, currItemType As String
Dim i As Integer
currItemType = Application.ActiveExplorer.CurrentFolder.DefaultMessageClass
If currItemType = "IPM.Note" Then 'mail Item types https://msdn.microsoft.com/en-us/library/office/ff861573.aspx
Set folder = Application.ActiveExplorer.CurrentFolder
folderPath = folder.Name
Do Until folder.Parent = "Mapi"
Set folder = folder.Parent
folderPath = folder.Name & "\" & folderPath
Loop
Debug.Print folderPath
End If
haven't got an problem with it yet. In your case, you can store the selection in a global variable, so you always know which folder was selected last.
This procedure will ask you to select the folder.
If you interrupt the code and examine the mFolderSelected or MySelectedFolder then you should be able to work something out:
Public Sub Test()
Dim MySelectedFolder As Variant
Set MySelectedFolder = PickFolder
End Sub
Public Function PickFolder() As Object
Dim oOutlook As Object 'Outlook.Application
Dim nNameSpace As Object 'Outlook.Namespace
Dim mFolderSelected As Object 'Outlook.MAPIFolder
On Error GoTo ERROR_HANDLER
Set oOutlook = CreateObject("Outlook.Application")
Set nNameSpace = oOutlook.GetNameSpace("MAPI")
Set mFolderSelected = nNameSpace.PickFolder
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'The commented out code will return only email folders. '
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''
If Not mFolderSelected Is Nothing Then
' If mFolderSelected.DefaultItemType = 0 Then
Set PickFolder = mFolderSelected
' Else
' Set PickFolder = Nothing
' End If
Else
Set PickFolder = Nothing
End If
Set nNameSpace = Nothing
Set oOutlook = Nothing
On Error GoTo 0
Exit Function
ERROR_HANDLER:
Select Case Err.Number
Case Else
MsgBox "Error " & Err.Number & vbCr & _
" (" & Err.Description & ") in procedure PickFolder."
Err.Clear
End Select
End Function
NB: This was written to be used in Excel and has late binding - you'll need to update it to work in Outlook (no need to reference Outlook for a start).

For Each loop: Some items get skipped when looping through Outlook mailbox to delete items

I wanted to develop VBA code that:
Loops through all email items in mailbox
If there are any type of other items say "Calendar Invitation" skips that item.
Finds out the emails with attachments
If attached file has ".xml" extension and a specific title in it, saves it to a directory, if not it keeps searching
Puts all email includes .xml attachments to "Deleted Items" folder after doing step 4 and deletes all emails in that folder by looping.
Code works perfect EXCEPT;
For example
There are 8 email received with ".xml" file attached to each one of them in your mailbox.
run the code
you will see only 4 of the 8 items are processed successfully, other 4 remain in their positions.
If you run the code again, now there would be 2 items processed successfully and other 2 remain in your mailbox.
Problem: After running the code, it is supposed to process all files and deletes them all not the half of them in each run. I want it to process all items at a single run.
BTW, this code runs every time I open the Outlook.
Private Sub Application_Startup()
'Initializing Application_Startup forces the macros to be accessible from other offic apps
'Process XML emails
Dim InboxMsg As Object
Dim DeletedItems As Outlook.Folder
Dim MsgAttachment As Outlook.Attachment
Dim ns As Outlook.NameSpace
Dim Inbox As Outlook.Folder
Dim fPathTemp As String
Dim fPathXML_SEM As String
Dim fPathEmail_SEM As String
Dim i As Long
Dim xmlDoc As New MSXML2.DOMDocument60
Dim xmlTitle As MSXML2.IXMLDOMNode
Dim xmlSupNum As MSXML2.IXMLDOMNode
'Specify the folder where the attachments will be saved
fPathTemp = "some directory, doesn't matter"
fPathXML_SEM = "some directory, doesn't matter"
fPathEmail_SEM = "some directory, doesn't matter"
'Setup Outlook
Set ns = GetNamespace("MAPI")
Set Inbox = ns.Folders.Item("mailbox-name").Folders("Inbox")
Set DeletedItems = ns.Folders.Item("mailbox-name").Folders("Deleted Items")
'Loop through all Items in Inbox, find the xml attachements and process if they are the matching reponses
'On Error Resume Next
For Each InboxMsg In Inbox.Items
If InboxMsg.Class = olMail Then 'if it is a mail item
'Check for xml attachement
For Each MsgAttachment In InboxMsg.Attachments
If Right(MsgAttachment.DisplayName, 3) = "xml" Then
'Load XML and test for the title of the file
MsgAttachment.SaveAsFile fPathTemp & MsgAttachment.FileName
xmlDoc.Load fPathTemp & MsgAttachment.FileName
Set xmlTitle = xmlDoc.SelectSingleNode("//title")
Select Case xmlTitle.Text
Case "specific title"
'Get supplier number
Set xmlSupNum = xmlDoc.SelectSingleNode("//supplierNum")
'Save the XML to the correct folder
MsgAttachment.SaveAsFile fPathXML_SEM & xmlSupNum.Text & "_" & Format(Date, "yyyy-mm-dd") & ".xml"
'Save the email to the correct folder
InboxMsg.SaveAs fPathEmail_SEM & xmlSupNum.Text & "_" & Format(Date, "yyyy-mm-dd") & ".msg"
'Delete the message
InboxMsg.Move DeletedItems
Case Else
End Select
'Delete the temp file
On Error Resume Next
Kill fPathTemp & MsgAttachment.FileName
On Error GoTo 0
'Unload xmldoc
Set xmlDoc = Nothing
Set xmlTitle = Nothing
Set xmlSupNum = Nothing
End If
Next
End If
Next
'Loop through deleted items and delete
For Each InboxMsg In DeletedItems.Items
InboxMsg.Delete
Next
'Clean-up
Set InboxMsg = Nothing
Set DeletedItems = Nothing
Set MsgAttachment = Nothing
Set ns = Nothing
Set Inbox = Nothing
i = 0
End Sub
Likely cause: When you do this InboxMsg.Move, all of the messages in your inbox after the one that was moved are bumped up by one position in the list. So you end up skipping some of them. This is a major annoyance with VBA's For Each construct (and it doesn't seem to be consistent either).
Likely solution: Replace
For Each InboxMsg In Inbox.Items
with
For i = Inbox.Items.Count To 1 Step -1 'Iterates from the end backwards
Set InboxMsg = Inbox.Items(i)
This way you iterate backward from the end of the list. When you move a message to deleted items, then it doesn't matter when the following items in the list are bumped up by one, because you've already processed them anyway.
It's often not a good idea to modify the contents of a (sub)set of items while looping over them. You could modify your code so that it first identifies all of the items that need to be processed, and adds them to a Collection. Then process all the items in that collection.
Basically you shouldn't be removing items from the Inbox while you're looping through its contents. First collect all the items you want to process (in your Inbox loop), then when you're done looping, process that collection of items.
Here's some pseudo-code which demonstrates this:
Private Sub Application_Startup()
Dim collItems As New Collection
'Start by identifying messages of interest and add them to a collection
For Each InboxMsg In Inbox.Items
If InboxMsg.Class = olMail Then 'if it is a mail item
For Each MsgAttachment In InboxMsg.Attachments
If Right(MsgAttachment.DisplayName, 3) = "xml" Then
collItems.Add InboxMsg
Exit For
End If
Next
End If
Next
'now deal with the identified messages
For Each InboxMsg In collItems
ProcessMessage InboxMsg
Next InboxMsg
'Loop through deleted items and delete
For Each InboxMsg In DeletedItems.Items
InboxMsg.Delete
Next
End Sub
Sub ProcessMessage(InboxMsg As Object)
'deal with attachment(s) and delete message
End Sub

Getting attachment from outlook using Access VBA

I have a created folder in my outlook named "Reports". This folder contains emails with one attachment in each email. I would like to use ACCESS VBA to save the attachments from the "Reports" folder in Outlook to a local drive in my computer. here is the code I have so far, but gives me errors. Please help:
Sub GetAttachments()
Dim ns As NameSpace
Dim Inbox As Outlook.MAPIFolder
Dim folder As Outlook.MAPIFolder
Dim Item As Object
Dim Atmt As Attachment
Dim FileName As String
Dim i As Integer
Set ns = GetNamespace("MAPI")
Set Inbox = ns.Folders.Item("Reports") // I get an error in this line says an object could not be found
i = 0
If Inbox.Items.Count = 0 Then
MsgBox "There are no messages in the Inbox.", vbInformation, _
"Nothing Found"
Exit Sub
End If
For Each Item In Inbox.Items
For Each Atmt In Item.Attachments
FileName = "C:\Automation\" & Atmt.FileName
Atmt.SaveAsFile FileName // here is another error says method is not found
i = i + 1
Next Atmt
Next Item
Is your Reports folder within your Inbox folder? You may need to do something like this:
Set ns = GetNamespace("MAPI")
Set Inbox = ns.GetDefaultFolder(olFolderInbox)
Set RptFolder = Inbox.Folders("Reports")
Your syntax for saving attachments looks correct (apart from your comments not being correct for VBA). You could print out the Filename that you are creating to see if it's a valid name. And I assume that you have created the Automation folder that you mention.
Update:
Try declaring your Atmt as an Outlook.Attachment. There is such a thing as an Access.Attachment which does not have a SaveAsFile method, and it's probably picking that one up first. If you include the library name, you should get the one you need.
Update 2:
To get to your Reports folder, one way is to get the Inbox folder as you are currently doing, then get its parent, then get the Reports folder under that.
Set ns = GetNamespace("MAPI")
Set Inbox = ns.GetDefaultFolder(olFolderInbox)
Set Mailbox = Inbox.Parent
Set RptFolder = Mailbox.Folders("Reports")
Another way would be to scan the items under "ns" to find the one that starts with "Mailbox", then get the Reports folder under that. It seems a little more cumbersome than getting the parent of the inbox. That also seems cumbersome, but I couldn't find a way to get to the Mailbox folder directly.
Replace
For Each Item In Inbox.Items
For Each Atmt In Item.Attachments
FileName = "C:\Automation\" & Atmt.FileName
Atmt.SaveAsFile FileName // here is another error says method is not found
i = i + 1
Next Atmt
With.....
For Each Item In Inbox.Items
For Each Atmt In Item.Attachments
FileName = "C:\Automation\" & Atmt.FileName
Attachments.SaveAsFile FileName // here is another error says method is not found
i = i + 1
Next Atmt
Outlook does not have a problem with atmt in the reference however, MS Access does. This should fix your problem.
Davis Rogers
Replace
Dim Atmt As Attachment
with
Dim Atmt As Outlook.Attachment
It'll make Access find the correct Class for atmt.