Excel VBA code only works in debug mode - vba

I've already searched in a bunch of topics and no solution seemed to work for me.
I've an Excel macro file that sometimes works fine, but sometimes only works in stepping mode.
This is a sub inside a main sub that passes a value (message) to a spreadsheet from an Outlook Calendar by category (key). (for this code I adapted from Script to total hours by calendar category in Outlook) .The value goes into the row with the same name as the category and the week value in the column. I've tried the DoEvents and I thought it had worked, but when I tried to run it in a different computer it failed again.
Any ideas?
Option Explicit
Public keyArray
Sub totalCategories()
Dim app As New Outlook.Application
Dim namespace As Outlook.namespace
Dim calendar As Outlook.Folder
Dim appt As Outlook.AppointmentItem
Dim apptList As Outlook.Items
Dim apptListFiltered As Outlook.Items
Dim startDate As String
Dim endDate As String
Dim category As String
Dim duration As Integer
Dim outMsg As String
Dim firstDayOfTheYear As Date
'Going to be used to get start and end date
firstDayOfTheYear = Date
firstDayOfTheYear = "01/01/" & Right(firstDayOfTheYear, 4)
' Access appointment list
Set namespace = app.GetNamespace("MAPI")
Set calendar = namespace.GetDefaultFolder(olFolderCalendar)
Set apptList = calendar.Items
' Include recurring appointments and sort the list
apptList.IncludeRecurrences = True
apptList.Sort "[Start]"
' Get selected date
startDate = firstDayOfTheYear + 7 * (CInt(SelectWeek.week) - 1)
endDate = firstDayOfTheYear + 7 * (CInt(SelectWeek.week) - 1) + 6
startDate = Format(startDate, "dd/MM/yyyy") & " 00:01"
endDate = Format(endDate, "dd/MM/yyyy") & " 11:59 PM"
' Filter the appointment list
Dim strFilter As String
strFilter = "[Start] >= '" & startDate & "'" & " AND [End] <= '" & endDate & "'"
Set apptListFiltered = apptList.Restrict(strFilter)
' Loop through the appointments and total for each category
Dim catHours
Set catHours = CreateObject("Scripting.Dictionary")
For Each appt In apptListFiltered
category = appt.Categories
duration = appt.duration
If catHours.Exists(category) Then
catHours(category) = catHours(category) + duration
Else
catHours.Add category, duration
End If
Next
' Loop through the categories
Dim key
keyArray = catHours.Keys
DoEvents 'prevents a bug from happening --> in some cases the total hours weren't divided by categories
For Each key In keyArray
outMsg = catHours(key) / 60
'Print in Realizado sheet --> activities must be in range (name manager) as "atividades"
writeReport SelectWeek.week, outMsg, key
Next
' Clean up objects
Set app = Nothing
Set namespace = Nothing
Set calendar = Nothing
Set appt = Nothing
Set apptList = Nothing
Set apptListFiltered = Nothing
End Sub
Sub writeReport(week, message As String, key)
Dim ws As Worksheet
Dim i As Integer
Dim Activities, nActivities As Integer
Set ws = Sheets("5")
Activities = Range("activities")
nActivities = UBound(Activities)
DoEvents
For i = 1 To nActivities
DoEvents
If key = Cells(i + 8, 2).Value Then
ws.Cells(i + 8, week + 3).Value = CDbl(message)
Exit For
End If
Next i
End Sub

You need to handle errors explicitly so you know exactly what is going on. Trust me that this will save you HEAPS of time troubleshooting your own code, especially in VBA.
Common practice is something like "try, catch, finally".
Dim position as string
Sub foo()
position = "sub function short description"
On Error GoTo catch
Err.Clear
'do stuff
finally:
On Error Resume Next
'do cleanup stuff
Exit Sub
catch:
Debug.Print Right(ThisWorkbook.FullName, Len(ThisWorkbook.FullName) - 3) & ", _
Position: " & errorPosition & " , Error Code: [ " & Hex(Err.number) & "], _
Description: " & Err.Description & ""
Resume finally
End Sub

Problem solved!
From this:
If key = Cells(i + 8, 2).Value Then
ws.Cells(i + 8, week + 3).Value = CDbl(message)
Exit For
End If
To this:
If key = Activities(i, 1) Then
ws.Cells(i + 8, week + 3).Value = CDbl(message)
Exit For
End If

Related

Macro is failing on special characters from mailitem.Subject item (Run-time error '1004': Application-Defined or Object-Defined error)

I am trying to create a macro that will import specific email items from my inbox and everything works perfect until it breaks at the email with subject line "=?ANSI_X3.4-1968?Q?......".
It seems like the email was originally written in other language and somehow it gets converted into a block of special characters that macro can't recognize. Please see the code below. Your helps will be highly appreciated!
sub search_inbox()
Range("A2:D60").ClearContents
Dim ol as Outlook.Application
Dim ns as Outlook.Namespace
Dim fol as Outlook.Folder
Dim i as Object
Dim mi as Outlook.MailItem
Dim rh as Double
Dim START_DATE as Date
Dim x as Long
Dim sh as Worksheet
set sh = ThisWorkbook.Sheets("Test")
set ol = New Outlook.Application
set ns = ol.GetNamespace("MAPI")
set fol = ns.Folders(sh.Range("M1").value).Folders(sh.Range("M2").value)
START_DATE = sh.range("J8") + sh.range("J9")
END_DATE = sh.range("K8") + sh.range("K9")
for Each i in fol.items
if i.SentOn >= START_DATE And i.SentOn <= END_DATE And i.Class = olMail Then
n = n + 1
set mi = i
Cells(n+1, 1).Value = mi.SenderName
Cells(n+1, 2).Value = mi.Subject
Cells(n+1, 3).Value = mi.SenderEmailAddress
Cells(n+1, 4).Value = mi.ReceivedTime
Cells(n+1, 5).Value = mi.Categories
Cells(n+1, 6).Value = mi.Size
Else
End If
Next i
MsgBox (x - 1)
End Sub
The procedure below first creates a test string Subject and then analyses it. It looks at every character in turn. If it's a double width character it replaces it with a question mark. Finally, it issues a few message boxes about the result.
Private Sub FindSpecials()
' 262
Dim Subject As String ' test string
Dim Result As String ' Message string
Dim Char As String ' one character of Subject
Dim n As Integer ' loop counter: characters
Dim Revised As String ' Subject without special characteres
Subject = "Email about " & ChrW(19978) & ChrW(28023) & " traffic"
For n = 1 To Len(Subject)
Char = Mid(Subject, n, 1)
If Asc(Char) <> AscW(Char) Then
Result = Result & vbCr & "Character " & AscW(Char) & " = " _
& Char & " in position " & n
Revised = Left(Subject, n - 1) & "?" & Mid(Subject, n + 1)
End If
Next n
If StrComp(Subject, Revised) Then
MsgBox "The following special characters were found" & Result
MsgBox "This is the revised subject:" & vbCr & _
"""" & Revised & """"
Else
MsgBox "No double-width characters were found"
End If
End Sub
Applied to your project, the test string Subject should be created from mi.Subject, of course. The output Result can be discarded but the string Revised might be useful to you. Of course, you might replace the double width characters with something other than a question mark. My code just intends to demonstrate how the special characters can be extracted and replaced.

If statements - msgbox

I'm now trying to write an IF statement to say something to the effect of: If file is more than 5 days old, do not run macro. If more than 5 days old, run macro.
I would like to this to be a yes or no dialogue box. Here is my code. Please help. I'm still trying to learn this vba code.
Sub LastModifiedFile()
'Function FileLastModified(strFullFileName As String)
Dim fs As Object, f As Object, s As String, dtmodpath As String
dtmodpath = "\\jdshare\pdcmaterials\5_Tools\FTP\Cancelled_Report.txt"
Set fs = CreateObject("Scripting.FileSystemObject")
Set f = fs.GetFile(dtmodpath)
's = UCase(strFullFileName) & vbCrLf
s = f.DateLastModified
FileLastModified = s
Set fs = Nothing: Set f = Nothing
Range("E5").Value = FileLastModified
'If FileExists(strFullName) Then
'MsgBox FileLastModified(strFullName)
'Else
'MsgBox "File Older than 5 Years : " & vbNewLine & strFullName
'End If
'End Function
End Sub
Congrats for using correctly the .DateLastModified property!
Instead of the MsgBox-es call a function. The DateAdd() returns date, which is 5 days before the current date, thus it is easy to compare. This shows a MsgBox() which informs whether the file has more or less than 5 days from the last modification:
Option Explicit
Sub LastModifiedFile()
Dim fileObject As Object
Dim file As Object
Dim modPath As String
modPath = "\\jdshare\pdcmaterials\5_Tools\FTP\Cancelled_Report.txt"
Set fileObject = CreateObject("Scripting.FileSystemObject")
Set file = fileObject.GetFile(modPath)
If DateAdd("d", -5, Now) < file.DateLastModified Then
MsgBox "Less than 5 days."
Else
MsgBox "More than 5 days."
End If
End Sub
If you want to put a MsgBox in the whole story with Yes and No, then this should be ok:
Sub LastModifiedFile()
Dim fileObject As Object
Dim file As Object
Dim modPath As String
modPath = "\\jdshare\pdcmaterials\5_Tools\FTP\Cancelled_Report.txt"
Set fileObject = CreateObject("Scripting.FileSystemObject")
Set file = fileObject.GetFile(modPath)
Dim msgBoxStatement As String
If DateAdd("d", -5, Now) < file.DateLastModified Then
msgBoxStatement = "This file is NOT older than 5 days!" & vbCrLf & _
"Should it be deleted?"
Else
msgBoxStatement = "This file is older than 5 days!" & vbCrLf & _
"Should it be deleted?"
End If
Select Case MsgBox(msgBoxStatement, vbYesNo Or vbQuestion, "Delete?")
Case vbYes
'run the for deletion
Case vbNo
'do not run the code for deletion
End Select
End Sub
Use DateDiff function to compute your number of days.
Its not totally clear what you want to do with your Yes/No message box, here's an attempt :
Sub LastModifiedFile()
Dim fs As Object, f As Object, s As String, dtmodpath As String
Dim dtLastMod As Date
Dim intDays As Long
dtmodpath = "\\jdshare\pdcmaterials\5_Tools\FTP\Cancelled_Report.txt"
Set fs = CreateObject("Scripting.FileSystemObject")
Set f = fs.GetFile(dtmodpath)
dtLastMod = f.DateLastModified
' Here you compute the number of days between the file last mod date, and the current date
intDays = DateDiff("d", dtLastMod, Now)
Set fs = Nothing: Set f = Nothing
Range("E5").Value = dtLastMod
If intDays > 5 Then
If MsgBox("File is " & intDays & " days old, proceed with macro ?", vbYesNo, "Continue?") = vbYes Then
' RUN MACRO GOES HERE
End If
Else
MsgBox "File is " & intDays & " days old, cancelling"
End If
End Sub

PasteSpecial - Error 5342 - Specified data type is unavailable (Word 2010 code)

I wrote a simple code in Word 2010 VBA (I am new in VBA), which simply takes some tables and one graph from Excel and paste them into Word as OLEobjects. Everythink works fine, except when the code tries to paste the chart from Excel into Word. I got the "Error 5342 - the specified data type is unavailable". You can find it in the final part of the code.
Sub Copy_Tables_and_Graphs_OLE()
'''' Variables Definition ''''
Dim pgmExcel As Excel.Application
Dim table As Word.table
Dim month As String
Dim year As String
Dim path As String
Dim monthyear As String
Dim year_1 As String
Dim monthyear_1 As String
Dim path_1 As String
Dim ultimate_path As String
Dim range As String
Dim sure As Integer
Dim same As Integer
Dim month_1 As String
Dim n As String
Dim Figure As String
Dim BookmarkArray As Variant
Dim i As Variant
Dim lenght As Integer
Dim chart As Object
Dim fso As Object
'''' Date Inputs ''''
year = InputBox("Please insert year - yyyy")
month = InputBox("Please insert month - mm")
monthyear = year & month
'''' Path Section ''''
path = "hiddenpath" & year & "\\" & monthyear & "hidden path.xlsx"
MsgBox ("Path Value is:" & path)
sure = MsgBox("Confirm? - answer yes or no", vbYesNo)
If sure = vbYes Then
path = "hidden path" & year & "\\" & monthyear & "hidden path.xlsx"
ultimate_path = path
Else
year_1 = InputBox("Then please insert the right - yyyy")
month_1 = InputBox("Then please insert the right - mm")
monthyear_1 = year_1 & month_1
path_1 = "hidden path" & year_1 & "\\" & monthyear_1 & "hidden path.xlsx"
ultimate_path = path_1
End If
'''' BookMarks ''''
BookmarkArray = Array("Book1", "Book2", "Book3", "Book4")
''''For Each BookMark''''
For i = LBound(BookmarkArray) To UBound(BookmarkArray)
lenght = Len(BookmarkArray(i))
n = Mid(BookmarkArray(i), lenght, 1)
'''' Range Selection ''''
If n = 1 Then
range = "B4:E6"
End If
If n = 2 Then
range = "B9:E11"
End If
If n = 3 Then
range = "B14:E16"
End If
'''' Copy and Paste Excel Tables ''''
Set pgmExcel = CreateObject("Excel.Application")
pgmExcel.Workbooks.Open ultimate_path
same = MsgBox("Figure n° " & n & " . Is the range the same of the previous time?", vbYesNo)
If same = vbYes Then
range = range
Else
range = InputBox("Could you please me provide the new range?")
End If
If i < 3 Then
Dim s As Long
s = Selection.Start
pgmExcel.ActiveWorkbook.Sheets(1).range(range).Copy
ActiveDocument.Bookmarks(i + 1).Select
Selection.PasteSpecial Link:=True, Placement:=wdInLine, DataType:=wdPasteOLEObject
pgmExcel.Quit
MsgBox ("You copied range " & range & " from folder" & ultimate_path)
Else
pgmExcel.ActiveWorkbook.Sheets(1).ChartObjects(1).Copy
ActiveDocument.Bookmarks(i + 1).Select
''' !!!! IN THE LINE BELOW I GET THE ERROR 5342 (Specified data type is unavailable) !!!!!! '''''
Selection.PasteSpecial Link:=True, Placement:=wdInLine, DataType:=wdPasteOLEObject, DisplayAsIcon:=False
pgmExcel.Quit
MsgBox ("You copied range " & range & " from folder" & ultimate_path)
ActiveDocument.Save
Set fso = CreateObject("Scripting.FileSystemObject")
If Not fso.FolderExists(fldr_name) Then
fso.CreateFolder (fldr_name)
End If
ActiveDocument.SaveAs2 FileName:="hidden path.docx", FileFormat:=wdFormatDocumentDefault
End If
Next i
End Sub
This is a tricky one as Macro recorder doesn't help in this case.
The solution is not to refer just to an item from a ChartObjects collection, but to its Chart.ChartArea.
Change your code from
pgmExcel.ActiveWorkbook.Sheets(1).ChartObjects(1).Copy
to
pgmExcel.ActiveWorkbook.Sheets(1).ChartObjects(1).Chart.ChartArea.Copy
and it should work as expected.

Store ActiveWindow.SelectedSheets as an object to refer to later

I'm trying to write a macro that will create a table of contents, listing the name of each of the worksheets currently selected by the user, together with the number of the page on which it starts when printed. I've taken the code from this page and adapted it a little as below.
However, when the new worksheet ("Contents") is created, that becomes the active, selected sheet, such that I can no longer use ActiveWindow.SelectedSheets to refer back to the collection of worksheets selected by the user. So I would like to store that information before creating the new sheet. How can I do this?
I have tried assigning it to a variable of type Worksheets as you can see, but this generates an error message. (I also tried Collection but to no avail.)
Sub CreateTableOfContents()
' Determine if there is already a Table of Contents
' Assume it is there, and if it is not, it will raise an error
' if the Err system variable is > 0, you know the sheet is not there
Dim WST As Worksheet
Dim SelSheets As Worksheets
Set SelSheets = ActiveWindow.SelectedSheets
On Error Resume Next
Set WST = Worksheets("Contents")
If Not Err = 0 Then
' The Table of contents doesn't exist. Add it
Set WST = Worksheets.Add(Before:=Worksheets("blankMagnitude"))
WST.Name = "Contents"
End If
On Error GoTo 0
' Set up the table of contents page
WST.[A2] = "Table of Contents"
With WST.[A6]
.CurrentRegion.Clear
.Value = "Subject"
End With
WST.[B6] = "Page(s)"
WST.Range("A1:B1").ColumnWidth = Array(36, 12)
TOCRow = 7
PageCount = 0
' Do a print preview on all sheets so Excel calcs page breaks
' The user must manually close the PrintPreview window
Msg = "Excel needs to do a print preview to calculate the number of pages." & vbCrLf & "Please dismiss the print preview by clicking close."
MsgBox Msg
SelSheets.PrintPreview
' Loop through each sheet, collecting TOC information
For Each S In SelSheets
If S.Visible = -1 Then
S.Select
ThisName = ActiveSheet.Name
HPages = ActiveSheet.HPageBreaks.Count + 1
VPages = ActiveSheet.VPageBreaks.Count + 1
ThisPages = HPages * VPages
' Enter info about this sheet on TOC
WST.Select
Range("A" & TOCRow).Value = ThisName
Range("B" & TOCRow).NumberFormat = "#"
If ThisPages = 1 Then
Range("B" & TOCRow).Value = PageCount + 1 & " "
Else
Range("B" & TOCRow).Value = PageCount + 1 & " " ' & - " & PageCount + ThisPages
End If
PageCount = PageCount + ThisPages
TOCRow = TOCRow + 1
End If
Next S
End Sub
I just amended your code. Is this what you are trying? Honestly all you had to do was
Change Dim SelSheets As Worksheets to Dim SelSheets and your original code would have worked :)
Option Explicit
Sub CreateTableOfContents()
Dim WST As Worksheet, S As Worksheet
Dim SelSheets
Dim msg As String
Dim TOCRow As Long, PageCount As Long, ThisPages As Long
Dim HPages As Long, VPages As Long
Set SelSheets = ActiveWindow.SelectedSheets
On Error Resume Next
Application.DisplayAlerts = False
Worksheets("Contents").Delete
Application.DisplayAlerts = True
On Error GoTo 0
Set WST = Worksheets.Add(Before:=Worksheets("blankMagnitude"))
With WST
.Name = "Contents"
.[A2] = "Table of Contents"
.[A6] = "Subject"
.[B6] = "Page(s)"
.Range("A1:B1").ColumnWidth = Array(36, 12)
End With
TOCRow = 7: PageCount = 0
msg = "Excel needs to do a print preview to calculate the number of pages." & vbCrLf & "Please dismiss the print preview by clicking close."
MsgBox msg
SelSheets.PrintPreview
For Each S In SelSheets
With S
HPages = .HPageBreaks.Count + 1
VPages = .VPageBreaks.Count + 1
ThisPages = HPages * VPages
WST.Range("A" & TOCRow).Value = .Name
WST.Range("B" & TOCRow).NumberFormat = "#"
If ThisPages = 1 Then
WST.Range("B" & TOCRow).Value = PageCount + 1 & " "
Else
WST.Range("B" & TOCRow).Value = PageCount + 1 & " " ' & - " & PageCount + ThisPages
End If
PageCount = PageCount + ThisPages
TOCRow = TOCRow + 1
End With
Next S
End Sub
EDIT: One important thing. It's always good to use OPTION EXPLICIT :)
You could store references to each sheet;
function getSheetsSnapshot() as Worksheet()
dim shts() As Worksheet, i As long
redim shts(ActiveWindow.SelectedSheets.Count - 1)
for i = 0 to ActiveWindow.SelectedSheets.Count - 1
set shts(i) = ActiveWindow.SelectedSheets(i + 1)
next
getSheetsSnapshot = shts
end function
fetch & store them:
dim oldsel() as Worksheet: oldsel = getSheetsSnapshot()
do your stuff then refer back to the original selected sheets;
for i = 0 to ubound(oldsel)
msgbox oldsel(i).name
next
Dim wks as Worksheet, strName as String
For each wks in SelSheets
strName = strName & wks.Name & ","
Next
strName = Left(strName, Len(strName) -1)
Dim arrWks() as String
arrWks = Split(strName,",")
End Sub
Your will have all the selected sheets, by name, in an arrWks, which you can then process through. You could also add each sheet name to a collection as well in the loop making it smoother.
It's best to stay away from ActiveSheet as much as possible. In this way you can loop through array with a counter and process
So:
Dim intCnt as Ingeter
For intCnt = Lbound(arrWks) to UBound(arrWks)
Worksheets(arrWks(intCnt)).Activate
.... rest of code ....
Next
replaces
For Each S In SelSheets

Providing status updates for macro that goes into not responding state until completion

I have a VBA Macro to search through email archives.
When searching through tens of thousands of emails, (or even just a couple hundred on my test machine) it displays the status for a few seconds, then enters a Not Responding state while running through the rest of the emails.
This has led impatient users to close out of the task prematurely, and I would like to rectify this by providing status updates.
I have coded the following solution, and believe that the problem lies in the way the GarbageCollector functions in VBA during the Loop.
Public Sub searchAndMove()
UserForm1.Show
' Send a message to the user indicating
' the program has completed successfully,
' and displaying the number of messages sent during the run.
End Sub
Private Sub UserForm_Activate()
Me.Width = 240
Me.Height = 60
Me.Label1.Width = 230
Me.Label1.Height = 50
Dim oSelectTarget As Outlook.Folder
Dim oMoveTarget As Outlook.Folder
Dim oSearchCriteria As String
' Select the target folder to search and then the folder to
' which the files should be moved
Set oSelectTarget = Application.Session.PickFolder
Set oMoveTarget = Application.Session.PickFolder
oSearchCriteria = InputBox("Input search string: ")
Dim selectedItems As Outlook.Items
Set selectedItems = oSelectTarget.Items
Dim selectedEmail As Outlook.MailItem
Dim StatusBarMsg As String
StatusBarMsg = ""
Dim initialCount As Long
initialCount = selectedItems.count
Dim movedCounter As Long
movedCounter = 0
Dim x As Long
Dim exists As Long
' Function Loop, stepping backwards
' to prevent errors derived from modifying the collection
For x = selectedItems.count To 1 Step -1
Set selectedEmail = selectedItems.Item(x)
' Test to determine if the subject contains the search string
exists = InStr(selectedEmail.Subject, oSearchCriteria)
If Len(selectedEmail.Subject) > 999 Then
selectedEmail.Move oMoveTarget
Else:
If exists <> 0 Then
selectedEmail.Move oMoveTarget
movedCounter = (movedCounter + 1)
Else: End If
End If
Set selectedEmail = Nothing
StatusBarMsg = "Processing " & x & " out of " & initialCount & " messages."
UserForm1.Label1.Caption = StatusBarMsg
UserForm1.Repaint
Next x
Dim Msg As String
Dim Response
Msg = "SearchAndMove has detected and moved " & movedCounter & _
" messages since last run."
Response = MsgBox(Msg, vbOKOnly)
' Close the References to prevent a reference leak
Set oSelectTarget = Nothing
Set oMoveTarget = Nothing
Set selectedItems = Nothing
Set selectedEmail = Nothing
Unload Me
End Sub
Change the line
UserForm1.Repaint
to
DoEvents
Yes this will increase the execution time but in case there are thousands of emails then you don't have much of an option.
TIP:
Also you might want to change
StatusBarMsg = "Processing " & x & " out of " & initialCount & " messages."
to
StatusBarMsg = "Please do not interrupt. Processing " & x & " out of " & initialCount & " messages."
Also it is advisable to inform your user at the beginning of the process that it might take time and hence they can run the process when they are sure they do not want to work on that pc?
Something like this
Sub Sample()
Dim strWarning As String
Dim Ret
strWarning = "This process may take sometime. It is advisable to run this " & _
"when you don't intend to use the pc for sometime. Would you like to Continue?"
Ret = MsgBox(strWarning, vbYesNo, "Information")
If Ret <> vbYes Then Exit Sub
For x = SelectedItems.Count To 1 Step -1
'~~> Rest of the code
End Sub
HTH
Sid