Send email macro - vba

I am trying to send an email through a macro linking in the email body certain data on the spread sheet. I have written the following and it runs but it just does not do anything. Was hoping someone could show some light on the problem:
Sub Send_Email_4()
Dim edress As String
Dim subj As String
Dim total As String
Dim message As String
Dim outlookapp As Object
Dim outlookmailitem As Object
Dim path As String
Dim lastrow As Integer
Dim x As Integer
Dim header As String
Dim header1 As String
Dim header2 As String
Dim header3 As String
Dim header4 As String
Dim header5 As String
Dim header6 As String
Dim header7 As String
Dim header8 As String
Dim data As String
Dim Data1 As String
Dim Data2 As String
Dim data3 As String
Dim data4 As String
Dim data5 As String
Dim data6 As String
Dim data7 As String
Dim data8 As String
x = 5
Do While Sheet1.Cells(x, 1) <> ""
Set outlookapp = CreateObject("Outlook.Application")
Set outlookmailitem = outlookapp.createitem(0)
edress = Sheet1.Cells(x, 1)
total = Sheetl.Cells(52, 10)
subj = Sheet1.Cells(x, 2)
header = Sheet1.Cells(4, 3)
header1 = Sheet1.Cells(4, 4)
header2 = Sheet1.Cells(4, 5)
header3 = Sheet1.Cells(4, 6)
header4 = Sheet1.Cells(4, 7)
header5 = Sheet1.Cells(4, 8)
header6 = Sheet1.Cells(4, 9)
header7 = Sheet1.Cells(4, 10)
data = Sheet1.Cells(x, 2)
Data1 = Sheet1.Cells(x, 4)
Data2 = Sheet1.Cells(x, 5)
data3 = Sheet1.Cells(x, 6)
data4 = Sheet1.Cells(x, 7)
data5 = Sheet1.Cells(x, 8)
data6 = Sheet1.Cells(x, 9)
data7 = Sheet1.Cells(x, 10)
outlookmailitem.To = edress
outlookmailitem.cc = ""
outlookmailitem.bcc = ""
outlookmailitem.Subject = subj
outlookmailitem.Body = "Dear NAFDA FS Member," & vbCrLf & _
"Results for the annual FYR 2021 growth program are as follows," & _
vbCrLf & header & " " & header1 & " " & header2 & " " & header3 & " " & header4 & " " & header5 & " " & header6 & " " & header7 & _
vbCrLf & data & " " & Data1 & " & data2 &" _
& data3 & " " & data4 & " " & data5 & _
data6 & " " & data7 & " " _
& vbCrLf & "Best Regards"
outlookmailitem.display
lastrow = lastrow + 1
EMAILS = ""
x = x + 1
Loop
End Sub

I think that the way you are trying to do it might not be the best way. Although, I just looked and "cleaned" your code to be more "light".
In your code you have subj = Sheet1.Cells(x, 2) equal to data = Sheet1.Cells(x, 2)!!!
Sub Send_Email_4()
Dim oApp As Object
Dim oItem As Object
Dim edress$, subj$, total$, message$, path$
Dim header$, header1$, header2$, header3$, header4$, header5$, header6$, header7$, header8$
Dim data$, data1$, Data2$, data3$, data4$, data5$, data6$, data7$, data8$
Dim x As Integer: x = 5
Do While Sheet1.Cells(x, 1) <> ""
Set oApp = CreateObject("Outlook.Application")
Set oItem = oApp.CreateItem(0)
With Sheet1
subj = .Cells(x, 2)
edress = .Cells(x, 1)
'total = .Cells(52, 10)
header = .Cells(4, 3): header1 = .Cells(4, 4): header2 = .Cells(4, 5): header3 = .Cells(4, 6)
header4 = .Cells(4, 7): header5 = .Cells(4, 8): header6 = .Cells(4, 9): header7 = .Cells(4, 10)
data = .Cells(x, 2)
data1 = .Cells(x, 4): Data2 = .Cells(x, 5): data3 = .Cells(x, 6): data4 = .Cells(x, 7)
data5 = .Cells(x, 8): data6 = .Cells(x, 9): data7 = .Cells(x, 10)
End With
oItem.To = edress
oItem.cc = ""
oItem.bcc = ""
oItem.Subject = subj
oItem.Body = "Dear NAFDA FS Member," & vbCrLf & vbCrLf
oItem.Body = oItem.Body & "Results for the annual FYR 2021 growth program are as follows," & vbCrLf & vbCrLf
oItem.Body = oItem.Body & header & " " & header1 & " " & header2 & " " & header3 & " " & header4 & " " & header5 & " " & header6 & " " & header7 & vbCrLf
oItem.Body = oItem.Body & data & " " & data1 & " " & Data2 & " " & data3 & " " & data4 & " " & data5 & " " & data6 & " " & data7 & vbCrLf & vbCrLf
oItem.Body = oItem.Body & "Best Regards"
oItem.display
x = x + 1
Loop
End Sub

I put together my own sub for sending emails, as I use it in multiple processes - it separates out the making of the email (creating body text, attachments, selecting to addresses etc) from the actual nitty-gritty send actions.
My code as follows:
Sub SendEmail(Optional ToAddresses As String, Optional CcAddresses As String, _
Optional BccAddresses As String, Optional Subject As String, _
Optional Body As String, Optional AttachFiles As Variant = False, Optional AutoSend As Boolean = False)
'Adapted from https://www.rondebruin.nl/win/s1/outlook/bmail4.htm
Dim OutApp As Object
Dim OutMail As Object
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
With OutMail
.to = ToAddresses
.CC = CcAddresses
.Bcc = BccAddresses
.Subject = Subject
If Body Like "*<br>*" Then
.HtmlBody = Body
Else
.Body = Body
End If
If Not AttachFiles = False Then
If IsArray(AttachFiles) Then
For x = LBound(AttachFiles) To UBound(AttachFiles)
.Attachments.Add (AttachFiles(x))
Next
Else
.Attachments.Add (AttachFiles)
End If
End If
If AutoSend = True Then
.Send
Else
.Display
End If
End With
Set OutMail = Nothing
Set OutApp = Nothing
End Sub
If you put that in your module then your sub would get reduced down to something like the following:
Sub Send_Email_4()
Dim edress As String, subj As String, total As String, message As String
Dim path As String, lastrow As Integer, x As Integer
Dim header(0 to 8) As String, Data(0 to 8) as String, Body as String
x = 5
Do While Sheet1.Cells(x, 1) <> ""
edress = Sheet1.Cells(x, 1)
total = Sheetl.Cells(52, 10)
subj = Sheet1.Cells(x, 2)
For a = 0 to 7
header(a) = Sheet1.Cells(4, a + 3)
Data(a) = Sheet1.Cells(x, a + 3)
Data(0) = Sheet1.Cells(x, 2) 'because this one's out of pattern with the others.
Next
Body = "Dear NAFDA FS Member," & vbCrLf & _
"Results for the annual FYR 2021 growth program are as follows," & vbCrLf
For a = 0 to 7
Body = Body & Header(a) & " "
Next
Body = Body & vbCrLf
For a = 0 to 7
Body = Body & Data(a) & " "
Next
Body = Body & vbCrLf & "Best Regards"
Call SendEmail(ToAddresses:=edress, _
Subject:=subj, _
Body:=Body, _
AutoSend:=False)
lastrow = lastrow + 1
EMAILS = ""
x = x + 1
Loop
End Sub
While optimising that I noticed you'd managed to enclose " & data2 &" in quotation marks in the email body, maybe that was the problem? Also not sure why you had variables Header8 and data8 unless you were using them somewhere else? You'll notice I reduced all those other variables down to arrays.
Give it a try and let me know if it works

Related

VBA Replace last field in ALL rows within csv around double quotes?

On Error Resume Next
Set FileSysObj = CreateObject("Scripting.FileSystemObject")
Const ForReading = 1 ' Declare constant for reading for more clarity
Dim cntFile, strCSVFullFile, strCSVFile, strDIR, cntBadLines, cntAllLines, strArchiveDir, strSafeTime,strSafeDate
' -------------------------------------------------------------------------------------------
' Specify CSV file name from the input argument
strCSVFile = Wscript.Arguments(1) ' Transactions
strDIR = Wscript.Arguments(2) & "\" ' C:\Temp
strArchiveDir = Wscript.Arguments(3) & "\"
strSafeTime = Right("0" & Hour(Now), 2) & Right("0" & Minute(Now), 2) & Right("0" & Second(Now), 2)
strSafeDate = Year(Date) & Month(Date) & day(Date)
set folder = FileSysObj.getFolder(strDIR)
cntFile = 0
cntBadLines = 0
cntAllLines = 0
for each file in folder.Files
' check if the file is there and echo it.
if InStr(1,file.name,strCSVFile,1) <> 0 then
strCSVFullFile = file.name
cntFile = cntFile + 1
end if
next
if cntFile > 1 or cntFile = 0 then
' error and end
Wscript.Echo "Error - only 1 file required for this process. There are " & cntFile & " file(s) in the directory"
WScript.Quit
end if
wscript.echo "Checking the file " & strCSVFullFile & " in " & strDIR
NoOfCols = Wscript.Arguments(0) ' usually 8
strTemp = "temp.csv"
strmissing = "missingdata.csv"
Set objOutFile = FileSysObj.CreateTextFile(strDIR & strTemp,True)
Set objOutFileM = FileSysObj.CreateTextFile(strDIR & strmissing,True)
Set inputFile = FileSysObj.OpenTextFile(strDIR & strCSVFullFile, ForReading, True)
' Set inputFile as file to be read from
Dim row, column, outline
Dim fields '(7) '8 fields per line
inputFile.ReadAll 'read to end of file
outline = ""
ReDim MyArray(inputFile.Line-2,NoOfCols) 'current line, minus one for header, and minus one for starting at zero
inputFile.close 'close file so that MyArray can be filled with data starting at the top
Set inputFile = FileSysObj.OpenTextFile(strDIR & strCSVFullFile, ForReading, True) 'back at top
strheadLine = inputFile.ReadLine 'skip header , but keep it for the output file
objOutFile.Write(strheadLine & vbCrLf)
anyBadlines = False
badlineflag = False
Do Until inputFile.AtEndOfStream
fullLine = inputFile.Readline
fields = Split(fullLine,",") 'store line in temp array
For column = 0 To NoOfCols-1 'iterate through the fields of the temp array
myArray(row,column) = fields(column) 'store each field in the 2D array with the given coordinates
'Wscript.Echo myArray(row,column)
if myArray(row,0) = " " or myArray(row,1) = " " then
badlineflag = True
'missline = myArray(row,0) & ", " & myArray(row,1) & ", " & myArray(row,2) & ", " & myArray(row,3) & ", " & myArray(row,4) & ", " & myArray(row,5) & ", " & myArray(row,6) & ", " & myArray(row,7)
'Wscript.Echo missline
'Exit For
end if
if column = NoOfCols-1 then
outline = outline & myArray(row,column) & vbCrLf
else
outline = outline & myArray(row,column) & ","
'csvFile = Regex.Replace(csvFile, "(,\s*?"".*?)(,)(\s+.*?""\s*?,)", "$1$3") 'TEST
end if
Next
cntAllLines = cntAllLines + 1
' Wscript.Echo outline
if badlineflag = False then
objOutFile.Write(fullLine & vbCrLf)
else
' write it somewhere else, drop a header in the first time
if anyBadlines = False Then
objOutFileM.Write(strheadLine & vbCrLf)
End if
objOutFileM.Write(outline)
cntBadLines = cntBadLines + 1
badlineflag = False
anyBadlines = True
end if
outline = ""
row = row + 1 'next line
Loop
objOutFile.Close
objOutFileM.Close
inputFile.close
Wscript.Echo "Total lines in the transaction file = " & cntAllLines
Wscript.Echo "Total bad lines in the file = " & cntBadLines
The below line is able to work as it contains 7 commas (8 columns).
URXW_99,BYQ0JC6,2603834418,2017-10-30,Test,4.962644,2278.0000,ABC
The below line will throw an error as a result of more commas than 7 in the script.
URXW_99,BYQ0JC6,2603834418,2017-10-30,Test,4.962644,2278.0000,Redburn, Europe. Limited
If greater than 7 commas in the CSV file line, the aim is to wrap it all greater than 7 into one field.
E.g. how do you replace Redburn, Europe. Limited string with double quotes as it is one name.
For example, in a text file it would appear like below:
URXW_99,BYQ0JC6,2603834418,2017-10-30,Test,4.962644,2278.0000,"Redburn, Europe. Limited"
Is there a way to write a VB or VBA script to do the above and save it as a .csv file (which is opened via notepad to check the double quotes)?
Option Explicit
Option Compare Text
Public Sub ConvertFile()
Dim lngRowNumber As Long
Dim strLineFromFile As String
Dim strSourceFile As String
Dim strDestinationFile As String
strSourceFile = "U:\Book3.csv"
strDestinationFile = "U:\Book4.csv"
Open strSourceFile For Input As #1
Open strDestinationFile For Output As #2
lngRowNumber = 0
Do Until EOF(1)
Line Input #1, strLineFromFile
strLineFromFile = Right(Replace(strLineFromFile, ",", " ", 1), 1000)
Write #2, strLineFromFile
strLineFromFile = vbNullString
Loop
Close #1
Close #2
End Sub
As I see, you use MS Access (due to Option Compare Text line), so there is better built-in instruments for this task.
Use DoCmd.TransferText for it.
1st step is to create output specification via:
Here you can setup delimiters, even that differs from ", and handle other options.
After that you can use your set-up specification via following command
DoCmd.TransferText acExportDelim, "TblCustomers_export_spec", "TblCustomers", "C:\test\1.txt", True
In this case all characters escaping would be done through built-in instruments. It seems to be more easier to correct this code further.
As mentioned, there is VBScript workaround. For given input data, following function will do desired actions for given string:
Option Explicit
Function funAddLastQuotes( _
strInput _
)
Dim arrInput
arrInput = Split(strInput, ",")
Dim intArrSize
intArrSize = UBound(arrInput)
Dim intCurrentElement
Dim strOutput
Dim intPreLastElement
intPreLastElement = 6
For intCurrentElement = 1 To intPreLastElement
strOutput = strOutput & "," & arrInput(intCurrentElement)
Next
Dim strOutputLastField
For intCurrentElement = intPreLastElement + 1 To intArrSize
strOutputLastField = strOutputLastField & "," & arrInput(intCurrentElement)
Next
strOutputLastField = Right(strOutputLastField, Len(strOutputLastField) - 1)
strOutput = Right(strOutput, Len(strOutput) - 1)
strOutput = strOutput & "," & """" & strOutputLastField & """"
funAddLastQuotes = strOutput
End Function
MsgBox funAddLastQuotes("RXW_99,BYQ0JC6,2603834418,2017-10-30,Test,4.962644,2278.0000,Redburn, Europe,,, Limited")
Finally, here is working VBScript solution.
Option Explicit
Const ColumnsBeforeCommadColumn = 6
Function funAddLastQuotes( _
strInput _
)
Dim arrInput
arrInput = Split(strInput, ",")
Dim intArrSize
intArrSize = UBound(arrInput)
Dim intCurrentElement
Dim strOutput
Dim intPreLastElement
intPreLastElement = ColumnsBeforeCommadColumn
For intCurrentElement = 1 To intPreLastElement
strOutput = strOutput & "," & arrInput(intCurrentElement)
Next
Dim strOutputLastField
If (intPreLastElement + 1) < intArrSize _
Then
For intCurrentElement = intPreLastElement + 1 To intArrSize
strOutputLastField = strOutputLastField & "," & arrInput(intCurrentElement)
Next
Else
strOutputLastField = strOutputLastField & "," & arrInput(intArrSize)
End If
strOutputLastField = Right(strOutputLastField, Len(strOutputLastField) - 1)
strOutput = Right(strOutput, Len(strOutput) - 1)
strOutput = strOutput & "," & """" & strOutputLastField & """"
funAddLastQuotes = strOutput
End Function
Public Sub ConvertFile( _
strSourceFile _
)
Dim objFS
Dim strFile
Dim strTemp
Dim ts
Dim objOutFile
Dim objFile
Set objFS = CreateObject("Scripting.FileSystemObject")
Dim strLine
Dim strOutput
Dim strRow
strFile = strSourceFile
strTemp = strSourceFile & ".tmp"
Set objFile = objFS.GetFile(strFile)
Set objOutFile = objFS.CreateTextFile(strTemp,True)
Set ts = objFile.OpenAsTextStream(1,-2)
Do Until ts.AtEndOfStream
strLine = ts.ReadLine
objOutFile.WriteLine funAddLastQuotes(strLine)
Loop
objOutFile.Close
ts.Close
objFS.DeleteFile(strFile)
objFS.MoveFile strTemp,strFile
End Sub
ConvertFile "C:\!accsoft\_in.csv"
You should change following part: ConvertFile "C:\!accsoft\_in.csv as path to your file.
And ColumnsBeforeCommadColumn = 6 is the setting, at which column the chaos with commas begins

Get data type from closed workbook cell and vary action accordingly

I'm collecting metric values from many different worksheets in one overview sheet which will be used for generating a PowerBI dashboard.
Below is my code, i'm new to vba so it's probably not so elegant, but works for what i need, except for one thing.
Some of the metric values in these sheets are integers, others have data type percentage.
If the value in the metric sheet has number format %, for example "10" formatted as %, it gets taken as 0,1 with the current code i have. I would like to multiply these percentages with 100 and add this number in the overview sheet. But I have difficulties finding out how i can extract the data type and if a percentage, multiply with 100, and if no percentage, get the value as is. Would anyone be able to help with that?
Many thanks in advance -
Function HasSheet(fPath As String, fName As String, sheetName As String)
On Error Resume Next
Dim f As String
f = "'" & fPath & "[" & fName & "]" & sheetName & "'!R1C1"
HasSheet = Not IsError(Application.ExecuteExcel4Macro(f))
If Err.Number <> 0 Then
HasSheet = False
End If
On Error GoTo 0
End Function
Sub CollectMetrics()
Dim id As Integer
Dim Ind As String
Dim MetricName As String
Dim Include1 As String
Dim Include2 As String
Dim Segment As String
Dim file As String
Dim filepath As String
Dim filename As String
Dim s As Boolean
Dim D As Date
Dim MonthNbr As Integer
Set sh1 = Worksheets("Metrics")
Set sh2 = Worksheets("Metadata")
NumRows = sh1.Range("A1", sh1.Range("A1").End(xlDown)).Rows.Count
For id = 2 To NumRows
MetricName = sh1.Range("A" & id).Value
Include1 = Application.WorksheetFunction.VLookup(MetricName, sh2.Range("B2:L100"), 9, True)
Include2 = Application.WorksheetFunction.VLookup(MetricName, sh2.Range("B2:L100"), 10, True)
Ind = Application.WorksheetFunction.VLookup(MetricName, sh2.Range("B2:L100"), 2, True)
filename = Ind & " " & MetricName & " 2018.xlsx"
If Include1 = "auto" And Include2 = "yes" Then
Segment = sh1.Range("B" & id).Value
file = "='https://xxx/[" & filename & "]" & Segment
filepath = "https://xxx/"
s = HasSheet(filepath, filename, Segment)
If s Then
D = sh1.Range("C" & id).Value
MonthNbr = Month(D)
sh1.Range("D" & id).Value = file & "'!D" & (MonthNbr + 13)
sh1.Range("E" & id).Value = file & "'!E" & (MonthNbr + 13)
sh1.Range("F" & id).Value = file & "'!F" & (MonthNbr + 13)
sh1.Range("G" & id).Value = file & "'!G" & (MonthNbr + 13)
sh1.Range("J" & id).Value = file & "'!D" & (MonthNbr + 40)
sh1.Range("K" & id).Value = file & "'!E" & (MonthNbr + 40)
sh1.Range("L" & id).Value = file & "'!F" & (MonthNbr + 40)
sh1.Range("M" & id).Value = file & "'!G" & (MonthNbr + 40)
sh1.Range("O" & id).Value = "values updated on " & Format(Now(), "dd-mm-yy")
Else
sh1.Range("O" & id).Value = "sheet available but segment missing"
End If
ElseIf Include2 = "no" Then
sh1.Range("O" & id).Value = "metric set to not yet include"
ElseIf Include1 = "manual" Then
sh1.Range("O" & id).Value = "metric to be manually updated"
End If
Next
MsgBox (" Update completed! ")
End Sub
I would try to avoid multiplying a percentage by 100 and adding a percent symbol, if there's the option to do it the "right way".
It's not a huge problem in this case, it's just better to create good habits. (And just for the record, the reason 10% gets taken as 0,1 is because 10% is 0,1.
Nonetheless, we need an easy way to display it as a percentage instead of a fraction of 1 (when applicable), and as with many tasks in Excel, there are multiple ways to accomplish the same thing.
This way took me the least thought:
Range("B1") = Range("A1") 'copies the value
Range("B1").NumberFormat = Range("A1") .NumberFormat 'copies the number format.
Changes I made:
The "cleanest" way to do this was with a small sub called copyNumber and adjusting the affected lines to use the new procedure.
I tidied indentation - which is important for organization and readability.
I added Option Explicit which is a good idea to have at the beginning of every module, to help recognize oversights such as...
sh1 and sh2 were not declared as Worksheets, so I added Dim statements for them - but squished them onto a line shared with their Set statements with : colons.
The other changes I made were purely cosmetic and more of a matter of perference, and obviously if you don't like those changes, don't use them. :-)
I got rid of the ElseIf's - I don't like them for the same reason indentation is important.
I used With..End statements to remove repetitive code (like Sh1. and Application.WorksheetFunction.)
I squished the variable declaration (Dim statements) from "a page" into 3 lines.
Adjusted Code:
Option Explicit
Function HasSheet(fPath As String, fName As String, sheetName As String)
On Error Resume Next
Dim f As String
f = "'" & fPath & "[" & fName & "]" & sheetName & "'!R1C1"
HasSheet = Not IsError(Application.ExecuteExcel4Macro(f))
If Err Then HasSheet = False
On Error GoTo 0
End Function
Sub copyNumber(rgeSrc As Range, rgeDest As Range)
rgeDest.Value = rgeSrc.Value ' copy number
rgeDest.NumberFormat = rgeSrc.NumberFormat ' copy number format
End Sub
Sub CollectMetrics()
Dim MetricName As String, Segment As String, Ind As String, Include1 As String, Include2 As String
Dim file As String, filePath As String, fileName As String
Dim MonthNbr As Integer, id As Integer, numRows As Integer
Dim sh1 As Worksheet: Set sh1 = Worksheets("Metrics")
Dim sh2 As Worksheet: Set sh2 = Worksheets("Metadata")
With sh1
numRows = Range("A1", Range("A1").End(xlDown)).Rows.Count
For id = 2 To numRows
MetricName = Range("A" & id)
With Application.WorksheetFunction
Include1 = .VLookup(MetricName, sh2.Range("B2:L100"), 9, True)
Include2 = .VLookup(MetricName, sh2.Range("B2:L100"), 10, True)
Ind = .VLookup(MetricName, sh2.Range("B2:L100"), 2, True)
End With
fileName = Ind & " " & MetricName & " 2018.xlsx"
If Include1 = "auto" And Include2 = "yes" Then
Segment = Range("B" & id)
file = "='https://xxx/[" & fileName & "]" & Segment
filePath = "https://xxx/"
If HasSheet(filePath, fileName, Segment) Then
MonthNbr = Month(Range("C" & id))
copyNumber .Range("D" & id), Range(file & "'!D" & (MonthNbr + 13))
copyNumber .Range("E" & id), Range(file & "'!E" & (MonthNbr + 13))
copyNumber .Range("F" & id), Range(file & "'!F" & (MonthNbr + 13))
copyNumber .Range("G" & id), Range(file & "'!G" & (MonthNbr + 13))
copyNumber .Range("J" & id), Range(file & "'!D" & (MonthNbr + 40))
copyNumber .Range("K" & id), Range(file & "'!E" & (MonthNbr + 40))
copyNumber .Range("L" & id), Range(file & "'!F" & (MonthNbr + 40))
copyNumber .Range("M" & id), Range(file & "'!G" & (MonthNbr + 40))
Range("O" & id) = "Values updated on " & Format(Now(), "dd-mm-yy")
Else
Range("O" & id) = "Sheet available but segment missing"
End If
Else
If Include2 = "no" Then
Range("O" & id) = "Metric set to not yet include"
Else
If Include1 = "manual" Then Range("O" & id) = "Metric to be manually updated"
End If
End If
Next id
End With
MsgBox "Update completed!"
End Sub
Just in case someone is looking for this approach in future, here is the final code i used:
Option Explicit
Function HasSheet(fPath As String, fName As String, sheetName As String)
On Error Resume Next
Dim f As String
f = "'" & fPath & "[" & fName & "]" & sheetName & "'!R1C1"
HasSheet = Not IsError(Application.ExecuteExcel4Macro(f))
If Err Then HasSheet = False
On Error GoTo 0
End Function
Sub CollectMetrics()
Dim MetricName As String, Segment As String, Ind As String, Include1 As String, Include2 As String, Include3 As String
Dim file As String, filePath As String, fileName As String
Dim MonthNbr As Integer, id As Integer, numRows As Integer
Dim sh1 As Worksheet: Set sh1 = Worksheets("Metrics")
Dim sh2 As Worksheet: Set sh2 = Worksheets("Metadata")
With sh1
numRows = Range("A1", Range("A1").End(xlDown)).Rows.Count
For id = 2 To numRows
MetricName = Range("A" & id)
With Application.WorksheetFunction
Include1 = .VLookup(MetricName, sh2.Range("B2:L100"), 9, True)
Include2 = .VLookup(MetricName, sh2.Range("B2:L100"), 10, True)
Include3 = .VLookup(MetricName, sh2.Range("B2:L100"), 11, True)
Ind = .VLookup(MetricName, sh2.Range("B2:L100"), 2, True)
End With
fileName = Ind & " " & MetricName & " 2018.xlsx"
If Include1 = "auto" And Include2 = "yes" Then
Segment = Range("B" & id)
file = "='https://xxxx/[" & fileName & "]" & Segment
filePath = "https://xxxx/"
If HasSheet(filePath, fileName, Segment) Then
MonthNbr = Month(Range("C" & id))
sh1.Range("D" & id).Value = file & "'!D" & (MonthNbr + 13)
sh1.Range("E" & id).Value = file & "'!E" & (MonthNbr + 13)
sh1.Range("F" & id).Value = file & "'!F" & (MonthNbr + 13)
sh1.Range("G" & id).Value = file & "'!G" & (MonthNbr + 13)
sh1.Range("H" & id).Value = file & "'!B" & (MonthNbr + 13) 'Actuals KPI Index
Select Case sh1.Range("H" & id).Value
Case "R"
sh1.Range("H" & id).Value = "3"
Case "Y"
sh1.Range("H" & id).Value = "2"
Case "G"
sh1.Range("H" & id).Value = "1"
End Select
sh1.Range("I" & id).Value = file & "'!D" & (MonthNbr + 40)
sh1.Range("J" & id).Value = file & "'!E" & (MonthNbr + 40)
sh1.Range("K" & id).Value = file & "'!F" & (MonthNbr + 40)
sh1.Range("L" & id).Value = file & "'!G" & (MonthNbr + 40)
sh1.Range("M" & id).Value = file & "'!B" & (MonthNbr + 13) 'YTD KPI Index
Select Case sh1.Range("M" & id).Value
Case "R"
sh1.Range("M" & id).Value = "3"
Case "Y"
sh1.Range("M" & id).Value = "2"
Case "G"
sh1.Range("M" & id).Value = "1"
End Select
Range("N" & id) = "Values updated on " & Format(Now(), "dd-mm-yy")
If Include3 = "%" Then ' multiply with 100 for percentages
sh1.Range("D" & id).Value = (sh1.Range("D" & id).Value) * 100
sh1.Range("E" & id).Value = (sh1.Range("E" & id).Value) * 100
sh1.Range("F" & id).Value = (sh1.Range("F" & id).Value) * 100
sh1.Range("G" & id).Value = (sh1.Range("G" & id).Value) * 100
sh1.Range("I" & id).Value = (sh1.Range("I" & id).Value) * 100
sh1.Range("J" & id).Value = (sh1.Range("J" & id).Value) * 100
sh1.Range("K" & id).Value = (sh1.Range("K" & id).Value) * 100
sh1.Range("L" & id).Value = (sh1.Range("L" & id).Value) * 100
End If
Else
Range("N" & id) = "Sheet available but segment missing"
End If
Else
If Include2 = "no" Then
Range("N" & id) = "Metric set to not yet include"
Else
If Include1 = "manual" Then Range("N" & id) = "Metric to be manually updated"
End If
End If
Next id
End With
MsgBox "Update completed!"
End Sub

Email Reminder doesnt triggered (to send) even Value Change from Formula in Excel

I'm new to VB in Excel.
I made an email reminder program in Excel with value changed from formula (calculation) in one of the cell.
The problem is the email reminder did not pop up even though the conditions are met.
But when I put manually the number (to meet the condition), the email reminder did pop up.
Please help to make the program run if the cell value from calculation met the condition of the program. Thanks!
Here is the code:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim completed As Boolean
Dim rowCount As Long
Dim i As Integer
Dim Objek As String
Dim SatKer As String
Dim Hari As String
Dim AlamatEmail As String
Dim xMailBody As String
rowCount = 2
If Target.Cells.Count > 1 Then Exit Sub
For i = 1 To 5
rowCount = rowCount + 1
Set xRg = Range("O" & CStr(rowCount))
Objek = ActiveSheet.Range("F" & CStr(rowCount)).Value
SatKer = ActiveSheet.Range("G" & CStr(rowCount)).Value
Hari = ActiveSheet.Range("O" & CStr(rowCount)).Value
AlamatEmail = ActiveSheet.Range("S" & CStr(rowCount)).Value
If xRg = Target And Target.Value < 4 Then
Call Mail_small_Text_Outlook(Objek, SatKer, Hari, AlamatEmail)
End If
Next i
End Sub
Sub Mail_small_Text_Outlook(Objek As String, SatKer As String, Hari As String, AlamatEmail As String)
Dim xOutApp As Object
Dim xOutMail As Object
Dim xMailBody As String
Set xOutApp = CreateObject("Outlook.Application")
Set xOutMail = xOutApp.CreateItem(0)
xMailBody = "Yth. Bapak Widi " & vbNewLine & vbNewLine & _
"Laporan Penilaian " & Objek & " milik " & SatKer & " mendekati batas akhir pengumpulan." & vbNewLine & _
"Laporan tersebut harus disubmit dalam " & Hari & " hari." & vbNewLine & vbNewLine & _
"Mohon cek status laporan penilaian untuk keterangan laporan lebih detail."
On Error Resume Next
With xOutMail
.To = AlamatEmail
.cc = ""
.BCC = ""
.Subject = "Laporan Penilaian " & Objek & " milik " & SatKer
.HTMLBody = xMailBody
.Display 'or use .Send
End With
On Error GoTo 0
Set xOutMail = Nothing
Set xOutApp = Nothing
End Sub
The following should do what you expect, the code will loop through column O and if the value calculated is less than 4 then it will .Display the email:
Private Sub Worksheet_Calculate()
Dim ws As Worksheet: Set ws = Sheets("Sheet1")
'declare and set your worksheet, amend as required
LastRow = ws.Cells(ws.Rows.Count, "O").End(xlUp).Row
'get the last row with data on Column O
Dim completed As Boolean
Dim rowCount As Long
Dim i As Integer
Dim Objek As String
Dim SatKer As String
Dim Hari As String
Dim AlamatEmail As String
Dim xMailBody As String
For i = 3 To LastRow 'loop from row 3 to last on Column O
Set xRg = Range("O" & i)
Objek = ws.Range("F" & i).Value
SatKer = ws.Range("G" & i).Value
Hari = ws.Range("O" & i).Value
AlamatEmail = ws.Range("S" & i).Value
If ws.Cells(i, "O").Value < 4 Then 'if value is less than 4 then send email
Call Mail_small_Text_Outlook(Objek, SatKer, Hari, AlamatEmail)
End If
Next i
End Sub
Sub Mail_small_Text_Outlook(Objek As String, SatKer As String, Hari As String, AlamatEmail As String)
Dim xOutApp As Object
Dim xOutMail As Object
Dim xMailBody As String
Set xOutApp = CreateObject("Outlook.Application")
Set xOutMail = xOutApp.CreateItem(0)
xMailBody = "Yth. Bapak Widi " & vbNewLine & vbNewLine & _
"Laporan Penilaian " & Objek & " milik " & SatKer & " mendekati batas akhir pengumpulan." & vbNewLine & _
"Laporan tersebut harus disubmit dalam " & Hari & " hari." & vbNewLine & vbNewLine & _
"Mohon cek status laporan penilaian untuk keterangan laporan lebih detail."
On Error Resume Next
With xOutMail
.To = AlamatEmail
.cc = ""
.BCC = ""
.Subject = "Laporan Penilaian " & Objek & " milik " & SatKer
.HTMLBody = xMailBody
.Display 'or use .Send
End With
On Error GoTo 0
Set xOutMail = Nothing
Set xOutApp = Nothing
End Sub

VBA- AutoFilter method of Range class falied

Please note I am not a regular programmer, I have sufficient understanding of coding. I am making a form in which ComboBox1 gives Month, ComboBox2 gives starting date, Combobox3 given ending date.
In the code below, arraystr1 should have values in format - 2, "10/4/2015", 2, "10/5/2015", 2, "10/6/2015", 2, "10/7/2015"
Now my arraystr1 is giving me the values in same format but when I am running the program, its giving me error - "AutoFilter method of Range class falied"
Dim Z As Long
Dim cbstr1 As String
Dim cbstr2 As String
Dim cbstr3 As String
Dim cbstr4 As String
Dim datestr1 As String
Dim datestr2 As String
Dim arraystr1 As String
Dim arraystr2 As String
Dim arraystr3 As String
Dim partstr1 As String
Dim partstr2 As String
partstr1 = " 2,"
partstr2 = ","
arraystr3 = ""
Select Case (ComboBox1.Text)
Case "January"
cbstr2 = "01"
//............//
Case "December"
cbstr2 = "12"
End Select
cbstr3 = ComboBox2.Text
cbstr4 = ComboBox3.Text
datestr1 = cbstr2 & "/" & cbstr3 & "/2015"
datestr2 = cbstr2 & "/" & cbstr4 & "/2015"
If cbstr3 = cbstr4 Then
arraystr1 = partstr1 & Chr(34) & datestr1 & Chr(34)
Else
For Z = cbstr3 To cbstr4
If Z = cbstr4 Then
**arraystr1** = arraystr3 & partstr1 & Chr(34) & datestr2 & Chr(34)
Else
arraystr2 = cbstr2 & "/" & Z & "/2015"
arraystr3 = partstr1 & Chr(34) & arraystr2 & Chr(34) & partstr2
End If
Next Z
End If
MsgBox (arraystr1)
Sheets("Sheet13").UsedRange.ClearContents
Sheets("Full data").Range("$A$1:$AB$45107").AutoFilter Field:=14, Operator:= _
xlFilterValues, Criteria2:=Array(**arraystr1**)
Selection.SpecialCells(xlCellTypeVisible).Select
Sheets("Full data").Select
Range("F:F,L:L,N:N,Q:Q,S:S").Select
Selection.Copy
Sheets("Sheet13").Select
Sheets("Sheet13").Cells(1, 1).Select
Sheets("Sheet13").Paste
Sheets("Sheet13").Range("C:C,E:E").Select
Application.CutCopyMode = False
Selection.NumberFormat = "General"

How to export email addresses from outlook meeting request

I sent an outlook (2010) meeting request to all company (4000+) and now I would like to send an additional email to those who accepted the request or accepted tentatively.
How do I do that? When I hit Contact Atendees --> New Email to Atendees in the ribbon it just send a response to all company and not only those who accepted. I also tried to export the contacts but it can only export the name alias and not the entire email addresses.
Any suggestions?
Thanks
The basis of the solution is found here Get Meeting Attendee List Macro
Here it is with minor changes.
Option Explicit
Sub GetAttendeeList()
Dim objApp As Outlook.Application
Dim objItem As Object
Dim objAttendees As Outlook.Recipients
Dim objAttendeeReq As String
Dim objAttendeeOpt As String
Dim objOrganizer As String
Dim dtStart As Date
Dim dtEnd As Date
Dim strSubject As String
Dim strLocation As String
Dim strNotes As String
Dim strMeetStatus As String
Dim strCopyData As String
Dim strCount As String
Dim ino, it, ia, ide
Dim x As Long
Dim ListAttendees As mailitem
'On Error Resume Next
Set objApp = CreateObject("Outlook.Application")
Set objItem = GetCurrentItem()
Set objAttendees = objItem.Recipients
On Error GoTo EndClean:
' Is it an appointment
If objItem.Class <> 26 Then
MsgBox "This code only works with meetings."
GoTo EndClean:
End If
' Get the data
dtStart = objItem.Start
dtEnd = objItem.End
strSubject = objItem.Subject
strLocation = objItem.location
strNotes = objItem.body
objOrganizer = objItem.Organizer
objAttendeeReq = ""
objAttendeeOpt = ""
Set ListAttendees = Application.CreateItem(olMailItem) ' <---
' Get The Attendee List
For x = 1 To objAttendees.count
strMeetStatus = ""
Select Case objAttendees(x).MeetingResponseStatus
Case 0
strMeetStatus = "No Response (or Organizer)"
ino = ino + 1
Case 1
strMeetStatus = "Organizer"
ino = ino + 1
Case 2
strMeetStatus = "Tentative"
it = it + 1
ListAttendees.Recipients.Add objAttendees(x) ' <---
Case 3
strMeetStatus = "Accepted"
ia = ia + 1
ListAttendees.Recipients.Add objAttendees(x) ' <---
Case 4
strMeetStatus = "Declined"
ide = ide + 1
End Select
If objAttendees(x).Type = olRequired Then
objAttendeeReq = objAttendeeReq & objAttendees(x).Name & vbTab & strMeetStatus & vbCrLf
Else
objAttendeeOpt = objAttendeeOpt & objAttendees(x).Name & vbTab & strMeetStatus & vbCrLf
End If
Next
strCopyData = "Organizer: " & objOrganizer & vbCrLf & "Subject: " & strSubject & vbCrLf & _
"Location: " & strLocation & vbCrLf & "Start: " & dtStart & vbCrLf & "End: " & dtEnd & _
vbCrLf & vbCrLf & "Required: " & vbCrLf & objAttendeeReq & vbCrLf & "Optional: " & _
vbCrLf & objAttendeeOpt & vbCrLf & "NOTES " & vbCrLf & strNotes
strCount = "Accepted: " & ia & vbCrLf & _
"Declined: " & ide & vbCrLf & _
"Tentative: " & it & vbCrLf & _
"No response: " & ino
'Set ListAttendees = Application.CreateItem(olMailItem)
ListAttendees.body = strCopyData & vbCrLf & strCount
ListAttendees.Display
ListAttendees.Recipients.ResolveAll ' <---
EndClean:
Set objApp = Nothing
Set objItem = Nothing
Set objAttendees = Nothing
End Sub
Building upon what #niton wrote, I've added support for checking against the Global Address List. This code could be extended to search all address lists available to you by iterating through myAddressLists, however, in most cases, that will probably be more than wanted.
Note that this isn't optimized for speed, but even a list with a few hundred people invited against a GAL of tens of thousands won't take a computer very long to iterate through. Since this doesn't get run very often, the time saved for optimizing this just didn't seem worth it.
Option Explicit
Sub GetAttendeeList()
Dim x As Integer
Dim y As Integer
Dim ino As Integer
Dim it As Integer
Dim ia As Integer
Dim ide As Integer
Dim objApp As Outlook.Application
Dim objItem As Object
Dim objAttendees As Outlook.Recipients
Dim objAttendeeReq As String
Dim objAttendeeOpt As String
Dim strAttendeeName As String
Dim strAttendeeEmail As String
Dim objOrganizer As String
Dim dtStart As Date
Dim dtEnd As Date
Dim strSubject As String
Dim strLocation As String
Dim strNotes As String
Dim strMeetStatus As String
Dim strCopyData As String
Dim strCount As String
Dim strCity As String
Dim folContacts As Outlook.MAPIFolder
Dim oContact As Outlook.ContactItem
Dim colItems As Outlook.Items
Dim oNS As Outlook.NameSpace
Dim ListAttendees As MailItem
Dim strNewRecord As String
Dim myAddressLists As AddressLists
Dim myAddressEntries As AddressEntries
Dim myAddressEntry As AddressEntry
Dim myExchangeUser As ExchangeUser
Dim myExchangeDL As ExchangeDistributionList
Dim myContactItem As ContactItem
On Error Resume Next
Set objApp = CreateObject("Outlook.Application")
Set oNS = Application.GetNamespace("MAPI")
Set folContacts = oNS.GetDefaultFolder(olFolderContacts)
Set colItems = folContacts.Items
Set myAddressLists = oNS.AddressLists
Set myAddressEntries = myAddressLists.Item("Global Address List").AddressEntries
Set objItem = GetCurrentItem()
Set objAttendees = objItem.Recipients
On Error GoTo EndClean:
' Is it an appointment
If objItem.Class <> 26 Then
MsgBox "This code only works with meetings."
GoTo EndClean:
End If
' Get the data
dtStart = objItem.Start
dtEnd = objItem.End
strSubject = objItem.Subject
strLocation = objItem.Location
strNotes = objItem.Body
objOrganizer = objItem.Organizer
objAttendeeReq = ""
objAttendeeOpt = ""
' Get The Attendee List
For x = 1 To objAttendees.Count
strMeetStatus = ""
Select Case objAttendees(x).MeetingResponseStatus
Case 0
strMeetStatus = "No Response (or Organizer)"
ino = ino + 1
Case 1
strMeetStatus = "Organizer"
ino = ino + 1
Case 2
strMeetStatus = "Tentative"
it = it + 1
Case 3
strMeetStatus = "Accepted"
ia = ia + 1
Case 4
strMeetStatus = "Declined"
ide = ide + 1
End Select
strAttendeeName = objAttendees(x).Name
strAttendeeEmail = objAttendees(x).Address
Set oContact = colItems.Find("[Email1Address] = '" & strAttendeeEmail & "'")
If Not oContact Is Nothing Then
Debug.Print "Test", oContact.BusinessAddressCity
strCity = oContact.MailingAddressCity & ", " & oContact.MailingAddressState
End If
If InStr(strAttendeeEmail, "#") = 0 Then
Debug.Print "Searching: " & objAttendees(x).Name
Set myAddressEntry = myAddressEntries.GetFirst()
Do While Not myAddressEntry Is Nothing
If myAddressEntry.Address Like objAttendees(x).Address Then
Debug.Print "Found: " & myAddressEntry.Name
Set myExchangeUser = myAddressEntry.GetExchangeUser()
Set myExchangeDL = myAddressEntry.GetExchangeDistributionList()
Set myContactItem = myAddressEntry.GetContact()
If Not myExchangeUser Is Nothing Then
strAttendeeEmail = myExchangeUser.PrimarySmtpAddress
End If
If Not myExchangeDL Is Nothing Then
strAttendeeEmail = myExchangeDL.PrimarySmtpAddress
End If
If Not myContactItem Is Nothing Then
strAttendeeEmail = myContactItem.Email1Address
End If
GoTo ContactFound
End If
Set myAddressEntry = myAddressEntries.GetNext()
Loop
End If
ContactFound:
strNewRecord = objAttendees(x).Name & vbTab & strAttendeeEmail & vbTab & strMeetStatus & vbTab & strCity & vbCrLf
If objAttendees(x).Type = olRequired Then
objAttendeeReq = objAttendeeReq & strNewRecord
Else
objAttendeeOpt = objAttendeeOpt & strNewRecord
End If
Next
strCopyData = "Organizer: " & objOrganizer & vbCrLf & "Subject: " & strSubject & vbCrLf & _
"Location: " & strLocation & vbCrLf & "Start: " & dtStart & vbCrLf & "End: " & dtEnd & _
vbCrLf & vbCrLf & "Required: " & vbCrLf & objAttendeeReq & vbCrLf & "Optional: " & _
vbCrLf & objAttendeeOpt & vbCrLf & "NOTES " & vbCrLf & strNotes
strCount = "Accepted: " & ia & vbCrLf & _
"Declined: " & ide & vbCrLf & _
"Tentative: " & it & vbCrLf & _
"No response: " & ino
Set ListAttendees = Application.CreateItem(olMailItem)
ListAttendees.Body = strCopyData & vbCrLf & strCount & vbCrLf & Time
ListAttendees.Display
EndClean:
Set objApp = Nothing
Set objItem = Nothing
Set objAttendees = Nothing
End Sub
Function GetCurrentItem() As Object
Dim objApp As Outlook.Application
Set objApp = Application
On Error Resume Next
Select Case TypeName(objApp.ActiveWindow)
Case "Explorer"
Set GetCurrentItem = objApp.ActiveExplorer.Selection.Item(1)
Case "Inspector"
Set GetCurrentItem = objApp.ActiveInspector.CurrentItem
End Select
Set objApp = Nothing
End Function