Write value to text file, not name of object - vba

I have some code which works great if putting the values into an Excel sheet but when I have tried to make it write to a text file it just writes the following over and over again:
objAddressEntry.Name
objAddressEntry.Name
Code below. I've left in the bit that originally wrote to Excel but it's commented out.
Sub GetOutlookAddressBook()
' Need to add reference to Outlook
'(In VBA editor Tools References MS Outlook #.# Library)
' Adds addresses to existing Sheet called Address and
' defines name Addresses containing this list
' For use with data Validation ListBox (Source as =Addresses)
On Error GoTo error
Dim objOutlook As Outlook.Application
Dim objAddressList As Outlook.AddressList
Dim objAddressEntry As Outlook.AddressEntry
Dim intCounter As Integer
Application.ScreenUpdating = False
' Setup connection to Outlook application
Set objOutlook = CreateObject("Outlook.Application")
Set objAddressList = objOutlook.Session.AddressLists("Global Address List")
Application.EnableEvents = False
' Clear existing list
' Sheets("Address").Range("A:A").Clear
' Create text file
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Dim oFile As Object
Set oFile = fso.CreateTextFile("C:\Users\username\Desktop\test.txt")
'Step through each contact and list each that has an email address
For Each objAddressEntry In objAddressList.AddressEntries
If objAddressEntry.Address <> "" Then
intCounter = intCounter + 1
Application.StatusBar = "Address no. " & intCounter & " ... " & objAddressEntry.Address
' Write to text file
oFile.WriteLine "objAddressEntry.Name" & vbNewLine
' Sheets("Address").Cells(intCounter, 1) = objAddressEntry.Name
DoEvents
End If
Next objAddressEntry
' Close the text file
oFile.Close
Set fso = Nothing
Set oFile = Nothing
' Define range called "Addresses" to the list of emails
' Sheets("Address").Cells(1, 1).Resize(intCounter, 1).Name = "Addresses"
error:
Set objOutlook = Nothing
Application.StatusBar = False
Application.EnableEvents = False
End Sub
Note that I rarely use VBA so I apologise if this is a trivial issue. I have struggled to find anything relevant in previous questions/answers as the search terms are quite broad.
My question is: How do I make it write the actual value rather than the name of the object?

If you just want to write the .Name then,
oFile.WriteLine objAddressEntry.Name & vbNewLine
... but if you want to write the .Name in quotes then,
oFile.WriteLine chr(34) & objAddressEntry.Name & chr(34) & vbNewLine

Related

How to apply a DASL filter in AdvancedSearch?

I adapted code so I can reply to the latest email.
I loop through a range of cells in my spreadsheet to get a string to find the email in my inbox or sent items.
The code sometimes finds and opens the email thread and sometimes it won't.
Is the syntax of my filter correct?
searchString = "urn:schemas:httpmail:textdescription ci_phrasematch" & supNumber
I commented these lines otherwise it won't stop looping:
While searchComplete = False
' DoEvents
Wend
The event handler OutlookApp_AdvancedSearchComplete never fires
The following code is saved in a class module:
Option Explicit
' Credits: Based on this answer: https://stackoverflow.com/questions/31909315/advanced-search-complete-event-not-firing-in-vba
' Event handler for outlook
Dim WithEvents OutlookApp As Outlook.Application
Dim outlookSearch As Outlook.Search
Dim outlookResults As Outlook.Results
Dim searchComplete As Boolean
Private Declare Function GetTickCount Lib "Kernel32" () As Long
Sub Minuterie(Milliseconde As Long)
Dim Arret As Long
Arret = GetTickCount() + Milliseconde
Do While GetTickCount() < Arret
DoEvents
Loop
End Sub
' Handler for Advanced search complete
Private Sub OutlookApp_AdvancedSearchComplete(ByVal SearchObject As Search)
MsgBox "The AdvancedSearchComplete Event fired."
searchComplete = True
End Sub
Sub SearchAndReply(program_number As Range, searchFolderName As String, searchSubFolders As Boolean)
' Declare objects variables
Dim customMailItem As Outlook.MailItem
Dim searchString As String
Dim resultItem As Integer
Dim supNumber As String
Dim compName As String
Dim strFilter As String
Dim OutlookApp As Outlook.Application
Dim strTag As String
Dim answer As VbMsgBoxResult
' Variable defined at the class level
'Dim outlookSearch As Outlook.Search
Set OutlookApp = New Outlook.Application
strTag = "BodySearch"
' Variable defined at the class level (modified by outlookApp_AdvancedSearchComplete when search is completed)
searchComplete = False
supNumber = "'" & program_number.Value & "'"
searchString = "urn:schemas:httpmail:textdescription ci_phrasematch" & supNumber
' Perform advanced search
Set outlookSearch = OutlookApp.AdvancedSearch(searchFolderName, searchString, searchSubFolders)
Minuterie 2000
' Wait until search is complete based on outlookApp_AdvancedSearchComplete event
' While searchComplete = False
' DoEvents
' Wend
' Get the results
Set outlookResults = outlookSearch.Results
If outlookResults.Count = 0 Then
program_number.Interior.Color = vbRed
Exit Sub
End If
' Sort descending so you get the latest
outlookResults.Sort "[SentOn]", True
' Reply only to the latest one
resultItem = 1
' Some properties you can check from the email item for debugging purposes
On Error Resume Next
Debug.Print outlookResults.Item(resultItem).SentOn, outlookResults.Item(resultItem).ReceivedTime, outlookResults.Item(resultItem).SenderName, outlookResults.Item(resultItem).subject
On Error GoTo 0
Set customMailItem = outlookResults.Item(resultItem).ReplyAll
' At least one reply setting is required in order to replyall to fire
'customMailItem.Body = "Just a reply text " & customMailItem.Body
customMailItem.HTMLBody = "<p> Thank you <p>" & customMailItem.HTMLBody
customMailItem.Display
program_number.Interior.Color = vbYellow
End Sub
I saved the following code in regular module in Excel:
Public Sub ProcessEmails()
Dim testOutlook As Object
Dim oOutlook As clsOutlook
Dim searchRange As Range
Dim subjectCell As Range
Dim OGDD_Programs As Range
Dim searchFolderName As String
Dim answer As VbMsgBoxResult
Dim Sup_ENg_Number As Range
' Start outlook if it isn't opened (credits: https://stackoverflow.com/questions/33328314/how-to-open-outlook-with-vba)
On Error Resume Next
Set testOutlook = GetObject(, "Outlook.Application")
On Error GoTo 0
If testOutlook Is Nothing Then
Shell ("OUTLOOK")
End If
' Initialize Outlook class
Set oOutlook = New clsOutlook
' Get the outlook inbox and sent items folders path (check the scope specification here: https://learn.microsoft.com/en-us/office/vba/api/outlook.application.advancedsearch)
searchFolderName = "'" & Outlook.Session.GetDefaultFolder(olFolderInbox).FolderPath & "','" & Outlook.Session.GetDefaultFolder(olFolderSentMail).FolderPath & "'"
'
' ' Loop through excel cells with subjects
' Set searchRange = ThisWorkbook.Worksheets("Sheet1").Range("A2:A4")
'set a reference to cells we are going to loop through
Set OGDD_Programs = ActiveSheet.Range("A2", "A" & Cells(Rows.Count, 1).End(xlUp).Row).SpecialCells(xlCellTypeVisible)
For Each Sup_ENg_Number In OGDD_Programs
If (Sup_ENg_Number.Interior.Color = vbYellow Or Sup_ENg_Number.Interior.Color = vbRed) Then
Else
' Only to cells with actual subjects
If Sup_ENg_Number.Value <> vbNullString Then
Call oOutlook.SearchAndReply(Sup_ENg_Number, searchFolderName, True)
answer = MsgBox("Do you want to exit subRoutine ?", vbYesNo)
If answer = vbYes Then
Exit Sub
End If
End If
End If
Next Sup_ENg_Number
MsgBox "Search and reply completed"
' Clean object
Set testOutlook = Nothing
End Sub
'Then add a class module and name it: clsOutlook
Why do you need AdvancedSearch (which is asynchronous)? Use MAPIFolder.Items.Find. (where MAPIFolder is the folder you need to search, such as the Inbox retrieved using Application.Session.GetDEfaultFolder(olFolderInbox)).
The code should first check if Instant Search is enabled in the default store to determine whether to use the ci_phrasematch keyword for an exact phrase match of "keyword" in the item body, or the like keyword to match any occurrence of "keyword" as an exact string or a substring in the item body. For example:
Dim filter As String
If (Application.Session.DefaultStore.IsInstantSearchEnabled) Then
filter = "#SQL=" & Chr(34) _
& "urn:schemas:httpmail:textdescription" & Chr(34) _
& " ci_phrasematch 'office'"
Else
filter = "#SQL=" & Chr(34) _
& "urn:schemas:httpmail:textdescription" & Chr(34) _
& " like '%office%'"
End If
The key benefits of using the AdvancedSearch method in Outlook are:
The search is performed in another thread. You don’t need to run another thread manually since the AdvancedSearch method runs it automatically in the background.
Possibility to search for any item types: mail, appointment, calendar, notes etc. in any location, i.e. beyond the scope of a certain folder. The Restrict and Find/FindNext methods can be applied to a particular Items collection (see the Items property of the Folder class in Outlook).
Full support for DASL queries (custom properties can be used for searching too). You can read more about this in the Filtering article in MSDN. To improve the search performance, Instant Search keywords can be used if Instant Search is enabled for the store (see the IsInstantSearchEnabled property of the Store class).
You can stop the search process at any moment using the Stop method of the Search class.
Read more about that in the Advanced search in Outlook programmatically: C#, VB.NET article.
Re: The event handler OutlookApp_AdvancedSearchComplete never fires.
I have to work around this event.
Option Explicit
Private Sub Get_LastMail_AdvSearch_URN_Subject()
Dim strSearch As String
Dim strFilter As String
Dim strScope As String
Dim objSearch As Search
Dim fldrNm As String
Dim rsts As results
Dim rstObj As Object
Debug.Print
strScope = "'Inbox', 'Sent Items', 'Deleted Items'"
'strScope = "'Inbox', 'Sent Items'"
Debug.Print "strScope............: " & strScope
strSearch = "test"
fldrNm = "Subject: " & strSearch
Debug.Print fldrNm
strFilter = "urn:schemas:httpmail:subject LIKE '%" & strSearch & "%'"
Debug.Print strFilter
Set objSearch = AdvancedSearch(scope:=strScope, Filter:=strFilter, SearchSubFolders:=True, Tag:="SearchFolder")
' The Application.AdvancedSearchComplete event is problem
' https://learn.microsoft.com/en-us/office/vba/api/outlook.application.advancedsearchcomplete
' https://stackoverflow.com/questions/31909315/advanced-search-complete-event-not-firing-in-vba
' Saving may be enough. This would be ideal but not on my machine.
' You may have to delete a previously generated search folder before subsequent runs
objSearch.Save fldrNm
DoEvents
Debug.Print fldrNm & " saved."
Set rsts = objSearch.results
Debug.Print " rsts.Count: " & rsts.Count
If rsts.Count = 0 Then
Debug.Print "Delay initiated."
' Delay to allow the search to complete
Dim waitTime As Long
Dim delay As Date
' Will surely be too little at the most inopportune time
waitTime = 1 ' in seconds - adjust as needed
Debug.Print vbCr & "Wait start: " & Now
delay = DateAdd("s", waitTime, Now)
Debug.Print "Wait until: " & delay
Do Until Now > delay
DoEvents
Loop
End If
Set rsts = objSearch.results
Debug.Print " rsts.Count: " & rsts.Count
If rsts.Count > 0 Then
rsts.Sort "[ReceivedTime]", True
Set rstObj = rsts(1)
Debug.Print rstObj.subject
Else
Debug.Print "no items found."
End If
End Sub

Using VBA to unzip file without prompting me once (choose "Yes to All" for any dialog box)

There is an unzipping code I'd like to adjust 4 my needs.
Sub Unzip()
Dim FSO As Object
Dim oApp As Object
Dim Fname As Variant
Dim FileNameFolder As Variant
Dim DefinePath As String
' Fname = Application.GetOpenFilename(filefilter:="Zip Files (*.zip), *.zip", MultiSelect:=False)
Fname = "C:\Users\michal\SkyDrive\csv\bossa\mstcgl.zip"
If Fname = False Then
'Do nothing
Else
'Destination folder
DefinePath = "C:\Users\michal\SkyDrive\csv\bossa\mstcgl_mst\" ' Change to your path / variable
If Right(DefinePath, 1) <> "\" Then
DefinePath = DefinePath & "\"
End If
FileNameFolder = DefinePath
' Delete all the files in the folder DefPath first if you want.
' On Error Resume Next
' Kill DefPath & "*.*"
' On Error GoTo 0
'Extract the files into the Destination folder
Set oApp = CreateObject("Shell.Application")
oApp.Namespace(FileNameFolder).CopyHere oApp.Namespace(Fname).items
' MsgBox "You find the files here: " & FileNameFolder
On Error Resume Next
Set FSO = CreateObject("scripting.filesystemobject")
FSO.deletefolder Environ("Temp") & "\Temporary Directory*", True
End If
End Sub
Somewhere here:
`Set oApp = CreateObject("Shell.Application")
oApp.Namespace(FileNameFolder).CopyHere`
a dialog box appears asking me if I want to overwrite the file that have the same names - and Yes I do want to overwrite them, but without answering the dialog box - I would like to hardcode it into the code, please.
I've found this page https://msdn.microsoft.com/en-us/library/windows/desktop/bb787866(v=vs.85).aspx but I just don't know how to add this parameter #16 which is "Respond with "Yes to All" for any dialog box that is displayed."
Can U help me with that?
And the last thing:
can You explain oApp.Namespace(Fname).items line for me.
I've really tried to guess it myself, but I thing I'm to short 4 this.
the code that results in no questions or no prompting of any kind is as follows:
Option Explicit
Sub Bossa_Unzip()
Dim FSO As Object
Dim oApp As Object ' oApp is the object which has the methods you're using in your code to unzip the zip file:
'you need to create that object before you can use it.
Dim Fname As Variant
Dim FileNameFolder As Variant ' previously Dim FileNameFolder As Variant
Dim DefinePath As String
' Fname = Application.GetOpenFilename(filefilter:="Zip Files (*.zip), *.zip", MultiSelect:=False)
Fname = "C:\Users\michal\SkyDrive\csv\bossa\mstcgl.zip"
If Fname = False Then
'Do nothing
Else
'Destination folder
DefinePath = "C:\Users\michal\SkyDrive\csv\bossa\mstcgl_mst\" ' Change to your path / variable
If Right(DefinePath, 1) <> "\" Then
DefinePath = DefinePath & "\"
End If
FileNameFolder = DefinePath
' Delete all the files in the folder DefPath first if you want.
' On Error Resume Next
' Kill DefPath & "*.*"
' On Error GoTo 0
'Extract the files into the Destination folder
Set oApp = CreateObject("Shell.Application") ' you need to create oApp object before you can use it.
oApp.Namespace(FileNameFolder).CopyHere oApp.Namespace(Fname).items, 16
'MsgBox "You'll find the files here: " & DefinePath
On Error Resume Next
Set FSO = CreateObject("scripting.filesystemobject")
FSO.deletefolder Environ("Temp") & "\Temporary Directory*", True
End If
End Sub
Of course this site helped me a lot - its CpyHere explanation site.
One thing I don't understand is why Fname and FileNumberFolder need to be declared as variant. In my opinion, they should be declared as String. Just look at this screenshot.
But when I declare them that way, the code gives me error.
Just look here, when the variables already have their values (first picture). The FileNameVariable and DefinePath variable have the exact same value, and it looks like a string 4 me. How is that necessary, that I need to declare another variable - FileNameVariable in that case (in 17th line) with the same value, but variant type.
please explain that to me, someone.

vba passing list of variable

I have already created a macro that creates individual files for me. Now having those files I have created another VBA job in outlook that will add the contact information to the e-mail, locate the needed file, and attach it to an e-mail. I need to do this to a list of about 50 different companies that I send these audits to. Currently I need to add a certain parameter to select what company I am using "V003" for example after this job is ran I go to the next one "V004" and so on.
I am looking for a way to provide VBA the list of 50 companies codes into which I have all as folders in a certain directory path. So when i kick off the job it will reference the folder named V003 in the directory path and use that as the VendorID variable I have created then loop back to the beginning and grab the next folder name V004 in the directory path and filter though until it gets to the last one.
Unless someone else has an idea that won't make me kick of the VBA job 50 times and pass in each variable. (Currently that's what I've been doing since I created these jobs and it's still a bit time consuming)
Dim GlobalVarEmail As String
Dim GlobalVarVendorName As String
Dim GlobalVendorId As String
Dim GlobalMonth As String
Dim GlobalYear As String
Dim GlobalAuditDate As String
Sub SendFilesbyEmail()
'the calling method of all sub methods.
GlobalVendorId = InputBox("What Vendor Letter are you trying to send out? (V Code: ex - V012)", "Vendor Code", "Type Here", 7500, 5000)
GlobalMonth = InputBox("What Month are you auditing for?(ex - Jan. Feb. Mar.)", "Month", "Type Here", 7500, 5000)
GlobalYear = InputBox("What year are you auditing for?(ex - 2016)", "Quarter", "Type Here", 7500, 5000)
GlobalAuditDate = InputBox("What is the audit date?(ex - 20160930)", "Quarter", "Type Here", 7500, 5000)
Call openExcel(GlobalVendorId)
Call SendAuditReport
End Sub
Public Function openExcel(UserReponse) As String
'this function is used to retrieve the vendor contact e-mail
Dim xlApp As Object
Dim sourceWB As Workbook
Dim sourceWS As Worksheet
Set xlApp = CreateObject("Excel.Application")
With xlApp
.Visible = False
.EnableEvents = True
End With
strFile = "G:\403(b)\User Folders\Chris W\SPARK Info\Contacts.xlsx"
Set sourceWB = Workbooks.Open(strFile, , False, , , , , , , True)
Set sourceWH = sourceWB.Worksheets("SPARK")
sourceWB.Activate
Rows("1:1").Select
Selection.AutoFilter
ActiveSheet.Range("$A$1:$H$100").AutoFilter Field:=1, Criteria1:=UserReponse
Range("F1").Select
GlobalVarEmail = Selection.End(xlDown).Value
Range("B1").Select
GlobalVarVendorName = Selection.End(xlDown).Value
ActiveWorkbook.Close SaveChanges:=False
End Function
Function SendAuditReport()
'this function will create a e-mail, (subjectline & body), attach the needed audit letter, and insert the needed vendor contact e-mail.
Dim Fname As String
Dim sAttName As String
Dim olApp As Outlook.Application
Dim olMsg As Outlook.MailItem
Dim olAtt As Outlook.Attachments
Set olApp = Outlook.Application
Set olMsg = olApp.CreateItem(0) ' email
Set olAtt = olMsg.Attachments
' send message
With olMsg
.Subject = GlobalVarVendorName & " " & GlobalMonth & " " & GlobalYear & " SPARK Audit"
.To = GlobalVarEmail
.CC = "SPARK#AXA.com"
.Attachments.Add "G:\403(b)\User Folders\Chris W\Spark Audit\" & GlobalAuditDate & "\00-Ran Reports\" & GlobalVendorId & "\SPARK Audit Report " & GlobalVarVendorName & ".xlsx"
'you can add attachments here just type .Attachments.Add "folder path"
.HTMLBody = "Hello, <br /><br /> Attached is the file
'.Send
.Display
End With
End Function
You can enumerate folder names as such:
Dim objFSO As Object
Dim objFolder As Object
Dim objSubFolder As Object
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder("C:\pathtoyourparentfolder")
For Each objSubFolder In objFolder.subfolders
MsgBox objSubFolder.Name
Next objSubFolder
objSubFolder.Name will be the name of the folder and you can just pass this to GlobalVendorID.

Embedding Several Images into an Email using VBA from Excel to Outlook and using CID

I'm trying to embed several graphs (as PNGs) from a Excel VBA Macro to Outlook.
The images embed however, it's not all 8 images but the first one repeated 8 times.
Sub Test()
Dim sheetNumber, size, i As Integer
Dim chartNames(), FNames() As String
Dim objChrt As ChartObject
Dim myChart As Chart
'Activate Charts Sheet
Sheets("GRAFICAS").Activate
'Calculate Number of Charts in Sheet
chartNumber = ActiveSheet.ChartObjects.Count
'Redimension Arrays to fit all Chart Export Names
ReDim chartNames(chartNumber)
ReDim FNames(chartNumber)
'Loops through all the charts in the GRAFICAS sheet
For i = 1 To chartNumber
'Select chart with index i
Set objChrt = ActiveSheet.ChartObjects(i)
Set myChart = objChrt.Chart
'Generate a name for the chart
chartNames(i) = "myChart" & i & ".png"
On Error Resume Next
Kill ThisWorkbook.Path & "\" & chartNames(i)
On Error GoTo 0
'Export Chart
myChart.Export Filename:=Environ$("TEMP") & "\" & chartNames(i), Filtername:="PNG"
'Save path to exported chart
FNames(i) = Environ$("TEMP") & "\" & chartNames(i)
Next i
'Declare the Object variables for Outlook.
Dim objOutlook As Object
'Verify Outlook is open.
On Error Resume Next
Set objOutlook = GetObject(, "Outlook.Application")
'If Outlook is not open, end the Sub.
If objOutlook Is Nothing Then
Err.Clear
MsgBox _
"Cannot continue, Outlook is not open.", , _
"Please open Outlook and try again."
Exit Sub
'Outlook is determined to be open, so OK to proceed.
Else
'Establish an Object variable for a mailitem.
Dim objMailItem As Object
Set objMailItem = objOutlook.CreateItem(0)
'Build the mailitem.
Dim NewBody As String
On Error Resume Next
With objMailItem
.To = "dummy#test.com"
.Subject = "Testing Lesson 31 email code"
.Importance = 1 'Sets it as Normal importance (Low = 0 and High = 2)
'Change the Display command to Send without reviewing the email.
' .Display
End With
For i = 1 To chartNumber
objMailItem.Attachments.Add FNames(i)
'Put together the HTML to embed
NewBody = NewBody + HTMLcode & "<div align=center>" & "<IMG src=cid: myChart" & i & ".png></img>" & "</div>"
Next
MsgBox NewBody
'Set the HTML body
objMailItem.HTMLBody = NewBody
'Display email before sending
objMailItem.Display
'Close the If block.
End If
Kill Fname
End Sub
MsgBox NewBody outputs:
and the final email looks like:
It should show all charts one below the other one, however it only takes myChart1.png and repeats it 8 times, despite the output of NewBody.
What am I doing wrong? I'm using Outlook 2013 and Excel 2013
UPDATE: I added another image and it seems to, in this case, repeat the last image I added 9 times (same as number of attached images). I'm guessing it's a problem with the cid, maybe ids aren't unique?
You must set the PR_ATTACH_CONTENT_ID property on the attachment appropriately to match the value of the cid attribute:
Set attach = objMailItem.Attachments.Add(FNames(i))
'Put together the HTML to embed
Dim cid
cid = "myChart" & i & ".png"
NewBody = NewBody + HTMLcode & "<div align=center>" & "<IMG src=cid:" & cid & "</img>" & "</div><br><br>"
Call attach.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001F", cid)

VBA Filter only returning exactly half the restricted criteria items

I am writing some VBA for Outlook, which is not something I often do. I have a strange problem with the following code:
Sub Archive()
Dim objSourceFolder As Folder
Dim OldMessages As Outlook.Items
Dim Allmessages As Outlook.Items
Dim objMessage As MailItem
Dim dtDate As Date
Dim strDate As String
Dim strProblemFiles As String
Dim objTargetFolder As Outlook.MAPIFolder
'how old is too old? give a number in months
'-----------------------------------------------
Const iMonthAge = 6
'-----------------------------------------------
strProblemFiles = ""
'locate the sourcefolder as the inbox
Set objSourceFolder = Application.Session.GetDefaultFolder(olFolderInbox)
'locate the target folder as the only one that can work according to IT - they will make this folder consistent apparently
Set objTargetFolder = Application.Session.Folders.GetFirst
Set objTargetFolder = objTargetFolder.Folders("Archive")
'There is this crappy thing on the filtering of messages where it takes a date string, not a date. So we need to calculate and then convert
'to the format that MS lists on the MSDN site
dtDate = DateAdd("M", -iMonthAge, Now())
strDate = Format(dtDate, "ddddd h:nn AMPM")
'apply a filter to only show messages older than the specified date, which have been read.
Set Allmessages = objSourceFolder.Items
Set OldMessages = Allmessages.Restrict("[Received] <= '" & strDate & "' AND [Unread] = False")
'let the poor user know what's going on - they can bail out now if they want
If MsgBox("There are " & OldMessages.Count & " old items to archive. They will be moved from your " & objSourceFolder.Name & _
" folder to your " & objTargetFolder.Name & " folder.", vbYesNo, "Archive Files Now?") = vbYes Then
'go through all the messages in the big list of messages older than the specified date, moving them if possible.
For Each objMessage In OldMessages
If TypeName(OldMessages.GetFirst) = "MailItem" Then
'do our shizzle
Else
'PRETTY MINIMAL ERROR CATCHING NEEDS IMPROVING
'write down the name of anything that isn't mail, I guess... need to work on this
strProblemFiles = strProblemFiles + vbCrLf + objMessage.Subject
GoTo errorcatch
'GoTo CarryOn
End If
'make a note for anyone who can look
Debug.Print objMessage.Subject
If objTargetFolder.DefaultItemType = olMailItem Then
If objMessage.Class = olMail Then
'There's nothing in errorcatch, but there will be
On Error GoTo errorcatch
'Move the item if you can
objMessage.Move objTargetFolder
End If
End If
'after an error, we jump here to go to the noxt item
CarryOn:
Next
Else
'if the user doesn't want to do it, we need to shut up shop and get the hell out of here
Set objSourceFolder = Nothing
Set OldMessages = Nothing
Set objMessage = Nothing
Set objTargetFolder = Nothing
Exit Sub
End If
'now we have done the whole thing, we can wipe down for fingerprints and exit through the window
Set objSourceFolder = Nothing
Set OldMessages = Nothing
Set objMessage = Nothing
Set objTargetFolder = Nothing
'reset the errors
On Error GoTo 0
'probably not going to be any that weren't mail items, but didn't cause a real error, but I guess we should show any we skipped.
If strProblemFiles <> "" Then MsgBox strProblemFiles
Exit Sub
'pathetic
errorcatch:
GoTo CarryOn
End Sub
Function FileExists(FileName As String) As Boolean
FileExists = (Dir(FileName) <> "")
End Function
Everything works... nearly. the first time I run the macro, it tells me that there are (e.g. 128 items ready to archive. It runs and I notice that there are still old messages in my inbox, so I run it again and it tells me there are 64 items ready for archive... then 32, 16 etc. halving the number of found messages each time. I cannot see why it would do this. Any ideas?
I should mention that this is running on Outlook 2010, using an Exchange.
Thanks for looking - all answers most appreciated!
Cheers,
Mark
Something like:
'...
Dim colMove As New Collection
'...
For Each objMessage In OldMessages
If objTargetFolder.DefaultItemType = olMailItem Then
If objMessage.Class = olMail Then colMove.Add objMessage
End If
Next
For Each objMessage In colMove
objMessage.Move objTargetFolder
Next
'...
The For Each issue is explained, and another method to move or delete items counting backwards is described here.
For Each loop: Just deletes the very first attachment
Option Explicit
Sub Archive()
Dim objSourceFolder As Folder
Dim OldMessages As Outlook.Items
Dim AllMessages As Outlook.Items
Dim objMessage As Object
Dim dtDate As Date
Dim strDate As String
Dim strProblemFiles As String
Dim objTargetFolder As Outlook.MAPIFolder
Dim colMove As New Collection
Dim objFolder As Outlook.MAPIFolder
Dim lngSize As Long
Dim objAnything As Object
Dim iMaxMBSize As Integer
Dim boolSentItems As Boolean
Dim catCategory As category
' Dim boolCatExists As Boolean
' Dim iColour As Integer
Set objSourceFolder = Nothing
Set OldMessages = Nothing
Set objMessage = Nothing
Set objTargetFolder = Nothing
'iColour = 18
'we are moving files, that's all, so we don't really need to worry too much about errors - if there is a problem, we can just skip the file
'without great negative effects.
On Error Resume Next
'how old is too old? give a number in months
'-----------------------------------------------
Const iMonthAge = 6
iMaxMBSize = 50
'-----------------------------------------------
'locate the sourcefolder as the inbox
boolSentItems = (MsgBox("Your inbox will be archived." & vbCrLf & _
"Do you want to also archive sent items?", vbYesNo, "Archive Options") = vbYes)
Set objSourceFolder = Application.Session.GetDefaultFolder(olFolderInbox)
'----------------------------------------------------------------------------------------------------------------------------------------
StartAgain:
'If you wish to assign a category to the folders rather than keep the folder structure when you archive, use this code and some other bits
'later on, which mention the categories and the variables mentioned here.
'Set objSourceFolder = Application.Session.GetDefaultFolder(olFolderInbox)
' boolCatExists = False
'For Each catCategory In Application.Session.Categories
' If catCategory.Name = "Archived from " & objSourceFolder.Name Then
' boolCatExists = True
' End If
'Next
'If boolCatExists = False Then
' Application.Session.Categories.Add "Archived from " & objSourceFolder.Name, iColour
'End If
'locate the target folder, which must be either in the same level as the inbox or lower
'----------------------------------------------------------------------------------------------------------------------------------------
Set objTargetFolder = SearchFolders(objSourceFolder.Parent, "Archive")
'if the target folder was not found, then we need to make it, in the root directory (the same level as the inbox - this is stipulated by IT)
If objTargetFolder Is Nothing Then
Set objTargetFolder = Application.Session.GetDefaultFolder(olFolderInbox).Folders.Add("Archive")
End If
'we are going to maintain the folder structure in the archive folder, for the inbox and sent items. This means we know exactly what to look for. If it isn't there,
'we just create it. I have used the search, rather than specifying the folders so that if the archiving is extended to more than just the inbobx and sent items, no
'change is needed.
If SearchFolders(objTargetFolder, objSourceFolder.Name) Is Nothing Then
Set objTargetFolder = objTargetFolder.Folders.Add(objSourceFolder.Name)
Else
Set objTargetFolder = objTargetFolder.Folders(objSourceFolder.Name)
End If
'There is this crappy thing on the filtering of messages where it takes a date string, not a date. So we need to calculate and then convert
'to the format that MS lists on the MSDN site
dtDate = DateAdd("M", -iMonthAge, Now())
strDate = Format(dtDate, "ddddd h:nn AMPM")
'apply a filter to only show messages older than the specified date, which have been read.
Set OldMessages = objSourceFolder.Items
Set OldMessages = OldMessages.Restrict("[Received] <= '" & strDate & "' AND [Unread] = False")
'let the poor user know what's going on - they can bail out now if they want
If OldMessages.Count > 0 Then
' If MsgBox("There are " & OldMessages.Count & " old items in your " & objSourceFolder.Name & ". Do you want to move them from your " & objSourceFolder.Name & _
' " folder to your " & objTargetFolder.Name & " folder.", vbYesNo, UCase(objSourceFolder.Name) + " Archive") = vbYes Then
'----------------------------------------------------------------------------------------------------------------------------------------
'go through all the messages in the big list of messages older than the specified date, moving them if possible.
'StatusForm.Show vbModeless
For Each objMessage In OldMessages
If TypeName(objMessage) = "MailItem" Then
'do our shizzle
Else
'if it is not a mailitem, there may be problems moving it - add it to the list instead.
strProblemFiles = strProblemFiles + vbCrLf + objSourceFolder.Name + ": " + objMessage.Subject
End If
'make a note for anyone who can look
Debug.Print objMessage.Subject
'probably pointless since we are only looking in the inbox and sent items, and making the mirrors ourselves, but check the folder is correct
If objTargetFolder.DefaultItemType = olMailItem Then
If objMessage.Class = olMail Then
'put the message in a nice stable collection for now - that way, we don't have to worry about the count changing etc
colMove.Add objMessage
End If
End If
Next objMessage
'----------------------------------------------------------------------------------------------------------------------------------------
'and here we have the actual move (and some optional text if you are using the categories)
For Each objMessage In colMove
'Move the item if you can
'objMessage.Categories = "Archived from " & objSourceFolder.Name
'objMessage.Save
objMessage.Move objTargetFolder
Next objMessage
'----------------------------------------------------------------------------------------------------------------------------------------
'Else
' 'if the user doesn't want to do it, we need to shut up shop and get the hell out of here
' Set objSourceFolder = Nothing
' Set OldMessages = Nothing
' Set objMessage = Nothing
' Set objTargetFolder = Nothing
' Exit Sub
'End If
Else
'if the count of all the old messages is not greater than 0
MsgBox "There are no messages from more than " & iMonthAge & " months ago in your " & objTargetFolder.Name & _
", so nothing will be archived.", vbExclamation, "Mailbox is Clean"
End If
'----------------------------------------------------------------------------------------------------------------------------------------
'finally, loop through literally all the items in the target folders and add up the sizes to see how much we have archived in total.
For Each objAnything In objTargetFolder.Parent.Items
lngSize = lngSize + objAnything.size
Next
'if they want to include the sent items in the archive, then change over the folder and do it all again
If boolSentItems = True Then
boolSentItems = False
Set objSourceFolder = SearchFolders(objSourceFolder.Parent, "Sent Items")
'iColour = iColour + 1
GoTo StartAgain
End If
'----------------------------------------------------------------------------------------------------------------------------------------
'once we have done all we can, let the user know about all the files that were skipped.
If strProblemFiles <> "" Then
MsgBox "The following items were skipped, so will still be in your mailbox" & vbCrLf & strProblemFiles, vbOKOnly, "Non-Mail Items"
Else
MsgBox "Archive complete", vbOKOnly, "Files Moved"
End If
'----------------------------------------------------------------------------------------------------------------------------------------
'the size of each file is listed in Bytes, so convert to MB to check the MB size and display, for convenience.
If lngSize / (1024 ^ 2) >= iMaxMBSize Then
MsgBox "Your archive folder takes up " & Round(lngSize / (1024 ^ 2), 0) & "MB; it is time to call IT to ask them to clear out the files", vbOKOnly, _
"Archive folder bigger than " & iMaxMBSize & "MB"
End If
'now we have done the whole thing, we can wipe down for fingerprints and exit through the window
Set objSourceFolder = Nothing
Set OldMessages = Nothing
Set objMessage = Nothing
Set objTargetFolder = Nothing
StatusForm.Hide
On Error GoTo 0
Exit Sub
'ErrorCatch:
'If you decide to add some error checking, put it in here, although as I say, I haven't bothered (see Declaration section at top)
End Sub
Public Function SearchFolders(objTopFolder As Outlook.MAPIFolder, strName As String)
Dim objFolder As Outlook.MAPIFolder
'look through all the sub folders at the level we started
For Each objFolder In objTopFolder.Folders
'If we find the one that we are looking for, great! we can get it and get out
If objFolder.Name = strName Then
Set SearchFolders = objFolder
Exit Function
'if we haven't found our magic folder yet, we need to carry on, by looking for any sub-sub folders this is done by calling the function itself on
'the current folder (which is by definition already one level lower than the starting location). if nothing is found, we,ll just carry on
Else
If objFolder.Folders.Count > 0 Then
Call SearchFolders(objFolder, strName)
End If
End If
Next
'the only way to exit the loop at this point is if all the folders have been searched and the folder we were looking for was not found.
Set SearchFolders = Nothing
End Function
the "StatusForm" user form that is referred to is a completely static form that just says "Archiving..." so the user is less likely to try mucking around in Outlook while the macro runs.