Copy list of emails to Outlook - excel

Can someone help me with below code? Here is a piece of code that is intended to copy a list of emails id's from "Sheet1" cells "B2" to "n" number of rows having data.
I am facing two issues with this.
1) HTMLBody text is not copied to email.
2) List of email recipient available at Sheet1, B2 onward is not getting copied on email recipient list ("To" list).
Thanks in advance!
Sub MeetingMacro()
'MsgBox Hour(Now)
If Weekday(Now, vbMonday) >= 6 And Hour(Now) > 12 Then
Exit Sub
End If
Application.ScreenUpdating = False
Dim pt As PivotTable
Set pt = ThisWorkbook.Sheets("Sheet2").PivotTables("PivotTable")
pt.RefreshTable
Application.CalculateUntilAsyncQueriesDone
Call saveAsXlsx1
Application.CalculateUntilAsyncQueriesDone
Call savefile
Application.CalculateUntilAsyncQueriesDone
Call Send_Range
'Call Send_Range
End Sub
Sub Send_Range()
Dim TBL As ListObject
ThisWorkbook.Activate
ThisWorkbook.EnvelopeVisible = False
ThisWorkbook.Sheets("Sheet2").Range("A1:B30").Select
ThisWorkbook.Activate
With ActiveSheet.MailEnvelope
SDest = ""
For iCounter = 2 To WorksheetFunction.CountA(Columns(3))
If SDest = "" Then
SDest = Cells(iCounter, 3).Value
SDest.Value.Select
Else
SDest = SDest & ";" & Cells(iCounter, 3).Value
End If
Next iCounter
.Item.To = SDest
.Item.CC = "someone#example.com"
.Item.Subject = "[URGENT] Meeting has been cancelled. "
.Item.HTMLBody = "Hello," & vbCrLf & "Meeting has been cancelled. Fresh invite will be sent soon.” & vbCrLf & "Regards"
.Item.Attachments.Add "C:\Attachment.xlsx" 'ActiveWorkbook.FullName
.Item.Send
End With
'MsgBox (TimeOfDay)
End Sub
'MsgBox (TimeOfDay)
Sub savefile()
Application.ScreenUpdating = False
ThisWorkbook.Activate
Application.ScreenUpdating = True
ThisWorkbook.Save
End Sub
Sub saveAsXlsx1()
ThisWorkbook.Worksheets(Array("Sheet2")).Copy
Application.DisplayAlerts = False
ActiveSheet.Shapes.Range("FetchData").Delete
ActiveWorkbook.SaveAs Filename:="C:\Attachment.xlsx"
ActiveWorkbook.Close
End Sub
Sub Meeting4()
ThisWorkbook.Application.DisplayAlerts = False
ActiveWorkbook.Save
ThisWorkbook.Close
End Sub

Say you have cells B2:B30 (all in the same column) in Sheet1, containing email addresses. What you want is to grab the values in these cells, and turn them into a one-dimensional array - that's done like this:
Dim values As Variant
values = Application.WorksheetFunction.Transpose(Sheet1.Range("B2:B30").Value)
With a one-dimensional array of email addresses, all you need to do is to turn it into a String. The Join function is made exactly for that:
Dim recipients As String
recipients = Join(values, ";")
That's all! ...assuming the cells all contain an email address string. If one cell contains an error value, expect trouble. If there are blanks, expect blanks (shouldn't make a difference though). If the range to grab isn't carved in stone, research how to make it more dynamic.
The HtmlBody is expecting an HTML-encoded string that contains HTML markup. If you only have plain text, use the Body property instead.

Related

Target Range with Outlook Issue

I have been struggling with this for some time. I am trying to use Target Range
to allow my Outlook VBA code to send emails to specific individuals from a dropdown list for each row in excel. I can get the first row to work properly but after that, each row below does not function properly. It will not populate a new email for that specific row.
I have tried various ways to get the code to work but it is a no-go. Here is my code. Again I am trying to get this to work with several rows within the sheet, works for the first row and nothing after. The drop-down list corresponds to another column within my sheet so it knows where to get the email from.
My Code is as followed:
Private Sub Worksheet_Change
Application.EnableEvents = False
Dim xRgSel As Range
Dim xOutApp As Object
Dim xMailItem As Object
Dim xMailBody As String
On Error Resume Next
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Set xRg = Range("G18:G500")
Set xRgSel = Intersect(Target, xRg)
ActiveWorkbook.Save
If Not xRgSel Is Nothing Then
Set xOutApp = CreateObject("Outlook.Application")
Set xMailItem = xOutApp.CreateItem(0)
xMailBody = "Cell(s) " & xRgSel.Address(False, False) & _
" in the worksheet '" & Me.Name & "' were modified on " & _
Format$(Now, "mm/dd/yyyy") & " at " & Format$(Now, "hh:mm:ss") & _
" by " & Environ$("username") & "."
With xMailItem
.To = Range("V18").Value
.Subject = "You have a new onboarding activity"
.Body = "Please complete this line item within 2 days of receiving this message. Please update spread sheet progress once you have completed the task."
.Attachments.Add (ThisWorkbook.FullName)
.Display
End With
Set xRgSel = Nothing
Set xOutApp = Nothing
Set xMailItem = Nothing
End If
Application.DisplayAlerts = True
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub

When a date is Entered the Macro will run

How do I add the code that will run automatically when a date is entered. If the code includes other cells - do they need to be populated first. The idea is when today's date is entered and email will be sent. The code Email works but I need for the code to run automatically.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If IsDate(Range("A1").Value) Then
'MsgBox "Plase enter a date in A1"
'response = MsgBox(msg, vbYesNo)
'If response = vbYes Then
email
Else
End If
'End If
End Sub
Sub email()
Dim r As Range
Dim cell As Range
Set r = Range("A1:S20")
For Each cell In r
If cell.Value = Date Then
Dim Email_Subject, Email_Send_From, Email_Send_To, _
Email_Cc, Email_Bcc, Email_Body As String
Dim Mail_Object, Mail_Single As Variant
Email_Subject = Cells(cell.Row, "C").Value
Email_Send_From = "Jean#test.com"
Email_Send_To = "Joe#test.com"
Email_Cc = ""
Email_Bcc = ""
Email_Body = "Hi " & Cells(cell.Row, "c").Value _
& vbNewLine & vbNewLine & _
Cells(cell.Row, "D").Value & _
" has been submited"
On Error GoTo debugs
Set Mail_Object = CreateObject("Outlook.Application")
Set Mail_Single = Mail_Object.CreateItem(0)
With Mail_Single
.Subject = Email_Subject
.To = Email_Send_To
.cc = Email_Cc
.BCC = Email_Bcc
.Body = Email_Body
.send
End With
End If
Next
Exit Sub
debugs:
If Err.Description <> "" Then MsgBox Err.Description
End Sub
I believe this is what you want. When the value in A1 is updated, it will first validate if the entry is a date. If entry is not a valid date, you will receive the first MsgBox in code.
If the entry is a valid date, the user will be asked if they want to send the email. If they select No, the macro will end. If they select Yes, your Email Sub will begin to execute.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Response As String
If Not IsDate(Range("A1").Value) Then
MsgBox "Plase enter a valid date in A1"
Else
Response = MsgBox("Do you want to send email?", vbYesNo)
If Response = vbYes Then Call Email
End If
End Sub
If you do not want the user to have to choose Yes or No to send the email, and you want it to happen as soon as the entry in A1 has been validated as a date, then change your IF statement to this:
If Not IsDate(Range("A1").Value) Then
MsgBox "Plase enter a valid date in A1"
Else
Call Email
End If
Side Note
You should also update this to only execute when a certain range (or maybe individual cell in this case) is changed. Otherwise, the macro will fire when ANY cell is changed. Instead, determine the range that should trigger this macro, and then call the macro when the changed cell overlaps (or Intersects) with your pre-determined range.

Create Emails from Excel Loop

I have this sample sheet:
My code currently goes through and creates emails based on the name in column H. So Approver1 gets one email for all his people. I have gotten it to de-dupe any repeats of their employee names. Example: Approver 1 gets an email that says 'please approve time for all of your employees below:' and then there is a list of names...Sample1, Sample2, and Sample3. The sheet will often have dupe employees for each approver, as shown in my sheet above.
The code works well for the first set of dupe names (there could be up to 10 of the same Approvers in a row, all getting one email), then runs fine through any singles.
When it hits the next set of repeated approvers it skips the first row in that group, then creates emails for every other division; so it skips a row until it gets to the end of the dupe approver section. So from the sheet, approver1 would get his email all set, then approver2 would get hers, but then approver3 becomes a mess.
How do I get this to loop correctly through an entire list, creating one email for each approver, with all the corresponding names of their people listed only once?
Sub DivisionApprovals()
Dim OutApp As Object
Dim OutMail As Object
Dim cell, lookrng As Range
Dim strdir As String
Dim strFilename As String
Dim sigString As String
Dim strBody As String
Dim strName As Variant
Dim strName1 As Variant
Dim strDept As Variant
Dim strName2 As String
Dim strbody2 As String
Dim strName3 As Variant
Application.ScreenUpdating = False
Set OutApp = CreateObject("Outlook.Application")
Set rng = ActiveSheet.UsedRange
r = 2
Do While r <= rng.rows.count
Set OutMail = OutApp.CreateItem(0)
Set strName = rng.Cells(r, 1)
Set strName3 = rng.Cells(r, 3)
strName2 = Trim(Split(strName, ",")(1))
strBody = "<Font Face=calibri>Dear " & strName2 & ", <br><br> Please approve the following divisions:<br><br>"
With OutMail
.To = rng.Cells(r, 2).Value
.Subject = "Please Approve Divisions"
List = strName3 & "<br>"
Do While rng.Cells(r, 1).Value = rng.Cells(r + 1, 1)
r = r + 1
Set strDept = rng.Cells(r, 3)
.Subject = "Approvals Needed!"
List = .HTMLBody & strDept & "<br>"
r = r + 1
.HTMLBody = List
Loop
.HTMLBody = strBody & "<B>" & List & "</B>" & "<br>" & Signature
.Display
End With
Set OutMail = Nothing
r = r + 1
Loop
Set OutApp = Nothing
End Sub
I deleted the previous answer, then un-deleted it in case you need that info. So as to not confuse anyone, the answer building from the OP's code is below.
DISCLAIMER: I am not a fan of the incrementing code style in the Do While, it make sit very difficult to chase errors but I understand the intention. I have included code below this in the way that my brain works and perhaps better coding style, you be the judge.
Alright #learningthisstuff I figured out what was going on, the code assumes the names are sorted. One thing not provided for is if the dept names are the same it will be listed multiple times, are the dept always unique for a person (no dupes?) if there are dupes that is different code.
This code works I just ran it as a macro on a dummy set. Big thing was the sort AND the incrementing logic, I changed a few things to make it more readable/understandable along the way.
I hope this helps you and you can modify as things change for you.
Sub Email_Macro()
'
' Email_Macro Macro
'
Dim OutApp As Object
Dim OutMail As Object
Dim cell, lookrng As Range
Dim strdir As String
Dim strFilename As String
Dim sigString As String
Dim strBody As String
Dim strName As Variant
Dim strName1 As Variant
Dim strDept As Variant
Dim strName2 As String
Dim strbody2 As String
Dim strName3 As Variant
Dim emailWS As Worksheet
Dim nameCol As Double
Dim deptCol As Double
Dim lastRow As Double
Dim startRow As Double
Dim r As Double
Dim depList As String
deptList = ""
Application.ScreenUpdating = False
Set OutApp = CreateObject("Outlook.Application")
Set emailWS = ThisWorkbook.ActiveSheet
startRow = 2 ' starting row
nameCol = 1 'col of name
deptCol = 3 'col of dept
'find the last row with a name in it from the name column
lastRow = emailWS.Cells(emailWS.Rows.Count, nameCol).End(xlUp).Row
'set variable to the starting row #
r = startRow 'this is where the counting begins
'sort the data first before going through the email process
'assumes these are the only columns 1 (nameCol) thru 3 (deptCol) to sort
'assumes you are sorting based on col 1 (nameCol)
emailWS.Range(Cells(startRow, nameCol), Cells(lastRow, deptCol)).Sort key1:=emailWS.Range(Cells(startRow, nameCol), Cells(lastRow, nameCol))
Do While r <= lastRow
Set OutMail = OutApp.CreateItem(0)
Set strName = emailWS.Cells(r, nameCol)
Set strName3 = emailWS.Cells(r, deptCol)
'careful the line below assumes there is always a comma separator in the name
strName2 = Trim(Split(strName, ",")(1))
strBody = "<Font Face=calibri>Dear " & strName2 & ", <br><br> Please approve the following divisions:<br><br>"
With OutMail
.To = emailWS.Cells(r, 2).Value
.Subject = "Please Approve Divisions"
deptList = strName3 & "<br>"
Do While emailWS.Cells(r, 1).Value = emailWS.Cells(r + 1, 1)
r = r + 1
Set strDept = emailWS.Cells(r, 3)
.Subject = "Approvals Needed!"
deptList = deptList & strDept & "<br>"
Loop
.HTMLBody = strBody & "<B>" & deptList & "</B>" & "<br>" & Signature
.Display
End With
Set OutMail = Nothing
'conditionally increment the row based on the name difference
If emailWS.Cells(r, 1).Value <> emailWS.Cells(r + 1, 1) Then
r = r + 1 'increment if there is a new name or no name
deptList = "" 'reset the department list
Else 'Do nothing
End If
Loop
Set OutApp = Nothing
End Sub
Screenshot:
To prove that I don't throw out comments without backing it up with some solution / mentoring? This is much easier for me to understand and troubleshoot. It steps through the rows in a very predictable fashion and we handle each row based on specified conditions. I also try and use variable names that will let you know what they are for.
Sub Email_Macro()
'
' Email_Macro Macro
'
Dim OutApp As Object 'email application
Dim OutMail As Object 'email object
Dim strBody As String 'first line of email body
Dim strName As String 'name in the cell we are processing
Dim strDept As String 'dept of the name we are processing
Dim previousName As String 'previous name processed
Dim nextName As String 'next name to process
Dim emailWS As Worksheet 'the worksheet selected wehn running macro
Dim nameCol As Double 'column # of names
Dim deptCol As Double 'column # of depts
Dim lastRow As Double 'last row of data in column
Dim startRow As Double 'row we wish to start processing on
Dim r As Double 'loop variable for row
'This will be the list of departments, we will build it as we go
Dim depList As String
Dim strSig As String 'email signature
strSig = "Respectfully, <br> Wookie"
deptList = "" 'empty intitialization
previousName = "" 'empty intialization
nextName = "" 'empty intialization
'Turn off screen updating
'Application.ScreenUpdating = False
'choose email application
Set OutApp = CreateObject("Outlook.Application")
'set worksheet to work on as active (selected sheet)
Set emailWS = ThisWorkbook.ActiveSheet
startRow = 2 ' starting row
nameCol = 1 'col of names, can also do nameCol = emailWS.Range("A1").Column
deptCol = 3 'col of depts, can also do deptCol = emailWS.Range("A3").Column
'** Advantage of the optional way is if you have many columns and you don't want to count them
'find the last row with a name in it from the name column
lastRow = emailWS.Cells(emailWS.Rows.Count, nameCol).End(xlUp).Row
'sort the data first before going through the email process using Range sort and a key
'assumes these are the only columns 1 (nameCol) thru 3 (deptCol) to sort
'assumes you are sorting based on col 1 (nameCol)
emailWS.Range(Cells(startRow, nameCol), Cells(lastRow, deptCol)).Sort key1:=emailWS.Range(Cells(startRow, nameCol), Cells(lastRow, nameCol))
'Set up our loop, it will go through every cell in the column we select in the loop
For r = startRow To lastRow
'Get the name and dept
'For the name we will split around the comma and take the second part of array (right of comma)
strName = Trim(Split(emailWS.Cells(r, nameCol), ",")(1))
strDept = emailWS.Cells(r, deptCol)
'if the next name is not blank (EOF)
If emailWS.Cells(r + 1, nameCol) <> "" Then
'assign the next name
nextName = Trim(Split(emailWS.Cells(r + 1, nameCol), ",")(1))
Else
'this is your EOF exit so assume a name
nextName = "Exit"
End If 'Else do noting on this If
If strName <> previousName Then
'Set our "new" name to previousName for looping
'process the "new" name
previousName = strName
'create the email object
Set OutMail = OutApp.CreateItem(0)
'Process as new email
With OutMail
.To = strName 'address email to the name
.Subject = "Please Approve Divisions" 'appropriate subject
deptList = strDept & "<br>" 'add the dept to dept list
'Build the first line of email body in HTML format
strBody = "<Font Face=calibri>Dear " & strName & ", <br><br> Please approve the following divisions:<br><br>"
End With
Else
'The name is the same as the email we opened
'Process Dept only by adding it to string with a line break
deptList = deptList & strDept & "<br>"
End If
'Do we send the email and get ready for another?
If strName <> nextName Then
'the next name is not the same as the one we are processing and we sorted first
'so it is time to send the email
OutMail.HTMLBody = strBody & "<B>" & deptList & "</B>" & "<br><br>" & strSig
OutMail.Display
Else 'Do Nohing
End If
Next r 'move to the next row
'nullify email reference
Set OutMail = Nothing
Set OutApp = Nothing
End Sub
If you want to guard against duplicate departments then I would do it like this, you can see where this goes there is only one end with:
End With
Else
'The name is the same as the email we opened
'Process Dept only by adding it to string with a line break
If InStr(deptList, strDept) = 0 Then
'Dept is not in the list so Add the department
deptList = deptList & strDept & "<br>"
Else
'Do nothing, the dept is already there
End If
End If
I suppose never give up. Everything is possible, maybe just outside of our current skillset (so get some help and keep learning).
Cheers - WWC
If you pivot your data, here is a way to loop through the pivot to get unique information by name.
Pivotted Data
Code
Option Explicit
Sub LoopPivot()
With Sheet1
Dim pt As PivotTable
Set pt = .PivotTables(1)
Dim nameField As PivotField
Set nameField = pt.PivotFields("Name")
Dim nameItem As PivotItem
For Each nameItem In nameField.PivotItems
Dim name As String
name = nameItem.Value
Dim emailField As PivotField
Set emailField = pt.PivotFields("email")
Dim emailItem As PivotItem
Set emailItem = emailField.PivotItems(nameItem.Position)
Dim email As String
email = emailItem.Value
Dim divisionName As Range
Dim division As String
division = vbNullString
For Each divisionName In nameItem.DataRange
division = division & "," & divisionName.Value
Next
division = Mid(division, 2, 255)
Debug.Print name
Debug.Print email
Debug.Print division
Next
End With
End Sub
Here is a little helper stub I use to find a unique list from column A and place that list in column C. Based on a button click. Modify as you wish.
Option Explicit
Private Sub CommandButton1_Click()
Dim thisWS As Worksheet
Dim firstRow As Double
Dim lastRow As Double
Dim workCol As Double
Dim dataRange As Range
Dim uniqueLast As Double
Dim uniqueCol As Double
Dim i As Double
Dim y As Double
Dim Temp As String
Dim found_Bool As Boolean
Set thisWS = ThisWorkbook.Worksheets("Sheet2")
workCol = thisWS.Range("A1").Column
firstRow = 1
uniqueLast = 1
uniqueCol = thisWS.Range("C1").Column
lastRow = thisWS.Cells(thisWS.Rows.Count, workCol).End(xlUp).Row
For i = firstRow To lastRow
Temp = Trim(UCase(thisWS.Range(Cells(i, workCol), Cells(i, workCol))))
Temp = Replace(Temp, "#", "")
found_Bool = False
For y = 1 To uniqueLast
If Temp = thisWS.Range(Cells(y, uniqueCol), Cells(y, uniqueCol)) Then
found_Bool = True
Else ' Do nothing
End If
Next y
If found_Bool = False Then
thisWS.Range(Cells(uniqueLast + 1, uniqueCol), Cells(uniqueLast + 1, uniqueCol)) = Temp
uniqueLast = uniqueLast + 1
Else
End If
Next i
End Sub
Once you do this you can lookup each name in the non unique column and get the appropriate dept for subject or other info.
What you want is really a pivot in VBA (name & dept(s), you could just vba the pivot, that is a little trickier but very doable.
'***************************************************
OK take what Scott has and its very workable. With regard to the pivot table itself a few "helpers". Again, either name the table and just update the range or delete it and make it each time. Do to the project I delete it every time here and keep using the same space to make picot after picot, every time the workbook is opened this scratch space is clear.
This is me creating a pivot of sales data, bear with me, I actually copy the pivot data afterwards to values and then add columns to perform calculations, then I move that to a report, deleting the pivot and working table, basically this all happens away from what the user gets to see when they click a button:
'***************************
'Add Sales Pivot Table
'Last DR is the last data row, you can see it done several times, in the code below, once you do it you will always do it
'CalcSheet is the name of the worksheet in the workbook I am working on
'The range here is defined in Range Format, you could use a named range or use .Range(Cells(row,col),Cells(row,col)) there are several ways
'I name the pivot table upon creation so I can manipulate it better
'I specify the target cell, upper left with which to begin the pivot table
ActiveWorkbook.PivotCaches.Create(SourceType:=xlDatabase, SourceData:= _
CalcSheet.Range("K14:AY" & LastDR), Version:=xlPivotTableVersion15).CreatePivotTable _
TableDestination:=CalcSheet.Range("CA37"), TableName:="SalesPVT", DefaultVersion _
:=xlPivotTableVersion15
I set the pivot up in the format that I want and then I sort it based on one of the fields:
With CalcSheet.PivotTables("SalesPVT").PivotFields("Salesperson")
.Orientation = xlRowField
.Position = 1
End With
With CalcSheet.PivotTables("SalesPVT").PivotFields("Customer")
.Orientation = xlRowField
.Position = 2
End With
With CalcSheet.PivotTables("SalesPVT").PivotFields("DD Rev")
.Orientation = xlDataField
.Function = xlSum
.NumberFormat = "$#,##0"
End With
With CalcSheet.PivotTables("SalesPVT").PivotFields("Job Days")
.Orientation = xlDataField
.Function = xlSum
.NumberFormat = "#,##0"
End With
CalcSheet.PivotTables("SalesPVT").PivotFields("Salesperson").AutoSort _
xlDescending, "Sum of DD Rev"
Perhaps there is another way but now I do not know the dimensions of the pivot table (rows) do I? So I define them here based on the first column where I placed the pivot table and the anchor range I specified in creation:
'Find the last row of Pivot table Data
Dim LastPVTrow As Double
Dim FirstPVTrow As Double
Dim NumPVTrows As Double
Dim PivCol As Double
PivCol = CalcSheet.Range("CB37").Column
FirstPVTrow = CalcSheet.Range("CB37").Row
LastPVTrow = CalcSheet.Cells(Rows.Count, PivCol).End(xlUp).Row
NumPVTrows = LastPVTrow - FirstPVTrow
Here I make a column somewhere else based on the pivot data, your email could occur about right here if you wanted:
'make the Avg Rev/Job Day Column
For i = 1 To NumPVTrows ' four columns in this table
CalcSheet.Range("CD" & (100 + i)).NumberFormat = "$#,##0"
If CalcSheet.Range("CC" & (FirstPVTrow + i)) <> 0 Then
CalcSheet.Range("CD" & (100 + i)) = CalcSheet.Range("CB" & (FirstPVTrow + i)) / CalcSheet.Range("CC" & (FirstPVTrow + i))
Else
CalcSheet.Range("CD" & (100 + i)) = 0
End If
Next i
'Here I am going to leave a bunch of stuff out but it puts headers on my table that is only missing the pivot and adds some more columns and calculations, counts the values based on specified ranges etc and finds averages
'Then I copy the pivot table and delete it, happens every time a button is clicked and a new workbook is selected to process
'copy pivot table to get rid of it
CalcSheet.PivotTables("SalesPVT").TableRange1.Copy
'Paste it as values with formatting
CalcSheet.Range("CA100").PasteSpecial Paste:=xlPasteValuesAndNumberFormats, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
'Delete Sales Pivot from the file
CalcSheet.PivotTables("SalesPVT").TableRange1.Delete
'Clear Work Space
CalcSheet.Range("CA1:CN500").Clear
Once I have processed the sales persons, I do it again by customer in the same working scratch space, build a table make new columns and headers down below based on the data, copy the table as values and then after putting it into a report, delete it all, for the next go around. I format my little table before export: bolding the headers, putting grey on the sales person or the customer, the totals line is blue, I right align the numbers in the cell, there sis alot of code left out to focus on the pivot.
So here is similar pivot code building the table for customers
'***************************************
'Make the Customer Pivot and table
'***************************************
ActiveWorkbook.PivotCaches.Create(SourceType:=xlDatabase, SourceData:= _
CalcSheet.Range("K14:AY" & LastDR), Version:=xlPivotTableVersion15).CreatePivotTable _
TableDestination:=CalcSheet.Range("CA37"), TableName:="CustPVT", DefaultVersion _
:=xlPivotTableVersion15
With CalcSheet.PivotTables("CustPVT").PivotFields("Customer")
.Orientation = xlRowField
.Position = 1
End With
With CalcSheet.PivotTables("CustPVT").PivotFields("DD Rev")
.Orientation = xlDataField
.Function = xlSum
.NumberFormat = "$#,##0"
End With
With CalcSheet.PivotTables("CustPVT").PivotFields("Job Days")
.Orientation = xlDataField
.Function = xlSum
.NumberFormat = "#,##0"
End With
'Find the last row of Pivot table Data
FirstPVTrow = CalcSheet.Range("CA37").Row
LastPVTrow = CalcSheet.Cells(Rows.Count, PivCol).End(xlUp).Row
'LastPVTrow = CalcSheet.Range("CB37:CB500").Find((0), LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
NumPVTrows = LastPVTrow - FirstPVTrow
etc. etc. etc . . .
I am sure the users on here are a lot more elegant.
I strive for code that is readable and usually understandable by me (hopefully others) and limited by my skillset, you have to come back to this stuff months or years later, trust me it looks different than when you are "living in the moment of creation" Take the time to leave yourself bread crumbs, name your variables and your tables so they make sense. Try an use named ranges rather than "hard coding" ranges, I know I did it here, do as I say . . . not as I do. I will usually only do this in areas that will later be erased and wiped. No excuses but I was moving in a rush on this one.
Cheers
I'm using a different technic to solve the same problem with Excel. First of all I have a Function to open a new ADODB-Recordset:
Function RST_Excel(strExceldatei As String, strArbeitsblatt As String, strWHERE As String, Optional strBereich As String, _
Optional strDatenfelder As String = "*") As ADODB.Recordset
Dim i As Integer
Dim rst As ADODB.Recordset
Dim strConnection As String
Dim strSQL As String
On Error GoTo sprFehler
strConnection = "DRIVER={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};DBQ=" & strExceldatei
If global_con Is Nothing Then
Set global_con = New ADODB.Connection
With global_con
.Open strConnection
End With
End If
strSQL = "SELECT " & strDatenfelder & " FROM [" & strArbeitsblatt & "$" & strBereich & "] WHERE " & strWHERE
Set rst = New ADODB.Recordset
With rst
.Source = strSQL
.CursorLocation = adUseClient
.ActiveConnection = global_con
.Open
Set RST_Excel = rst
End With
sprEnde:
Set rst = Nothing
Exit Function
sprFehler:
Set rst = Nothing
Set RST_Excel = Nothing
End Function
Then I open the ADODB-Recordset from another Routine:
Dim strWHERE As String
Dim strFields As String
Dim rst_Recipients As ADODB.Recordset
strWHERE = "Surname IS NOT NULL AND Emailadress IS NOT NULL"
strFields = "Surname, Name, Emailadress, SMIME"
Set rst_Empfänger = RST_Excel(ThisWorkbook.FullName, "Email", strWHERE, "A1:M1000", strFields)
As the query is passed as an SQL-Statement you could also pass an Statement to get unique results.
The advance for me is that I could easily move through the Recordset:
With rst
.movefirst
do until .eof
debug.print .fields("surename").value
.movenext
loop
end with
I think you can use this to do what you want to do.
Make a list in Sheets("Sheet1") with :
In column A : Names of the people
In column B : E-mail addresses
In column C:Z : Filenames like this C:\Data\Book2.xls (don't have to be Excel files)
The Macro will loop through each row in "Sheet1" and if there is a E-mail address in column B
and file name(s) in column C:Z it will create a mail with this information and send it.
Sub Send_Files()
'Working in Excel 2000-2016
'For Tips see: http://www.rondebruin.nl/win/winmail/Outlook/tips.htm
Dim OutApp As Object
Dim OutMail As Object
Dim sh As Worksheet
Dim cell As Range
Dim FileCell As Range
Dim rng As Range
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
Set sh = Sheets("Sheet1")
Set OutApp = CreateObject("Outlook.Application")
For Each cell In sh.Columns("B").Cells.SpecialCells(xlCellTypeConstants)
'Enter the path/file names in the C:Z column in each row
Set rng = sh.Cells(cell.Row, 1).Range("C1:Z1")
If cell.Value Like "?*#?*.?*" And _
Application.WorksheetFunction.CountA(rng) > 0 Then
Set OutMail = OutApp.CreateItem(0)
With OutMail
.to = cell.Value
.Subject = "Testfile"
.Body = "Hi " & cell.Offset(0, -1).Value
For Each FileCell In rng.SpecialCells(xlCellTypeConstants)
If Trim(FileCell) <> "" Then
If Dir(FileCell.Value) <> "" Then
.Attachments.Add FileCell.Value
End If
End If
Next FileCell
.Send 'Or use .Display
End With
Set OutMail = Nothing
End If
Next cell
Set OutApp = Nothing
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
End Sub

VBA - Copy information to new workbook

I'm trying to do something that sounds incredibly simple but I can't figure out how to fit it into existing VBA code. The code below cycles through a pivot table 1 item at a time and copies that pivot table data out to a new workbook and emails to the staff member
All i need to add in is for it to copy (just values and formatting) a 13x2 table in the range E15:S16 on the same sheet as the pivot table, into the new workbook in the tab I've named "Monthly Forecast". with the loops etc i'm not sure how to get this into the code so it copies the pivot data and then the monthly forecast into the separate tab
Hope that makes sense, any help would be wonderful :)
Option Explicit
Sub PivotSurvItems()
Dim i As Integer
Dim sItem As String
Dim sName As String
Dim sEmail As String
Dim OutApp As Object
Dim OutMail As Object
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.DisplayAlerts = False
With ActiveSheet.PivotTables("PivotTable1")
.PivotCache.MissingItemsLimit = xlMissingItemsNone
With .PivotFields("Staff")
'---hide all items except item 1
.PivotItems(1).Visible = True
For i = 2 To .PivotItems.Count
.PivotItems(i).Visible = False
Next
For i = 1 To .PivotItems.Count
.PivotItems(i).Visible = True
If i <> 1 Then .PivotItems(i - 1).Visible = False
sItem = .PivotItems(i)
ActiveSheet.PivotTables("PivotTable1").PivotSelect "", xlDataAndLabel, True
Selection.Copy
Workbooks.Add
With ActiveWorkbook
.Sheets(1).Cells(1).PasteSpecial _
Paste:=xlPasteValuesAndNumberFormats
Worksheets("Sheet1").Columns("A:R").AutoFit
ActiveSheet.Range("A2").AutoFilter
sName = Range("C" & 2)
sEmail = Range("N" & 2)
Columns(1).EntireColumn.Delete
Columns(2).EntireColumn.Delete
Columns(2).EntireColumn.Delete
Columns(2).EntireColumn.Delete
Columns(10).EntireColumn.Delete
ActiveSheet.Name = "FCW"
Sheets.Add(After:=Sheets(Sheets.Count)).Name = "Monthly Forecast"
Worksheets("FCW").Activate
'create folder
On Error Resume Next
MkDir "C:\Temp\FCW" & "\" & sName
On Error GoTo 0
.SaveAs "C:\Temp\FCW" & "\" & sName & "\" & sItem & " " & Format(Now(), "DD-MM-YYYY") & ".xlsx", _
FileFormat:=xlOpenXMLWorkbook
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
On Error Resume Next
With OutMail
.To = sEmail
.CC = ""
.BCC = ""
.Subject = "Planning Spreadsheet"
.Attachments.Add ActiveWorkbook.FullName
.Send
End With
On Error GoTo 0
Set OutMail = Nothing
Set OutApp = Nothing
.Close
End With
Next i
End With
End With
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.DisplayAlerts = True
End Sub
Instead of changing visibility and cycling through all the items in the pivot table, assign the values to a 'table' (a range) and pass it to where you want it to go (it's much faster than using Excel's .copy and .PasteSpecial in VBA.
Also, I suggest that you copy all the data to an 'outputs' worksheet in the same workbook. When all the data has been copied, export that specific outputs worksheet into a new workbook. This way you avoid copying and pasting data between two different workbooks which can be prone to errors.
In your code, I would remove everything from the item cycling down until the Temp folder creation and replace it with something like the following:
'Copy values
Set rStartCell = ActiveSheet.Range("A1") 'Specify the top-left corner cell of the data you wish to copy
Set rTable_1 = ActiveSheet.Range(rStartCell, ActiveSheet.Range("Z" & rStartCell.End(xlDown).Row)) 'Change the Z column to the last column of the data you wish to copy. You can automate this by using something like Range(A1).end(xltoright).columns.count formula to grab the number of columns.
Debug.Print "rTable_1: " & rTable_1.Address & " -> " & rTable_1.Rows.Count & " x " & rTable_1.Columns.Count 'good to test exactly what you're copying
'Paste Values
Set rStartCell = Outputs.Range("A1") 'Change A1 to the cell of where you want to paste on the Outputs worksheet in your original workbook.
Set rTable_2 = Outputs.Range(rStartCell, rStartCell.Offset(rTable_1.Rows.Count - 1, rTable_1.Columns.Count - 1))
Debug.Print "rTable_2: " & rTable_2.Address & " -> " & rTable_2.Rows.Count & " x " & rTable_2.Columns.Count
rTable_2.Value = rTable_1.Value
rTable_1.Copy
rTable_2.PasteSpecial Paste:=xlPasteFormats 'to copy/paste those formats you need
'Copy Worksheet and open it in a new workbook
ThisWorkbook.Sheets("NAME OF OUTPUTS SHEET").Copy 'Using ThisWorkbook to point to the workbook holding this code.
ActiveSheet.Name = "FCW"
You can use this method to copy/paste that other table mentioned as well.

object required run time error '424'

i am getting object required run time error in below code at line , i checked sheet names they are correct but still showing same error Sheet1.Range("A1").Value = Date & " " & Time
Private Sub CommandButton1_Click()
Dim username As String
Dim password As String
username = TextBox1.Text
password = TextBox2.Text
Dim info
info = IsWorkBookOpen("D:\TMS_Project\username-password.xlsx")
If info = False Then
Workbooks.Open ("D:\TMS_Project\username-password.xlsx")
End If
Dim x As Integer
x = 2
Do While Cells(x, 1).Value <> ""
If Cells(x, 1).Value = username And Cells(x, 2).Value = password Then
MsgBox "Welcome!"
Sheet1.Range("A1").Value = Date & " " & Time
Selection.NumberFormat = "m/d/yyyy h:mm AM/PM"
UserForm1.Hide
ActiveWorkbook.Close True
End
Else
x = x + 1
End If
Loop
MsgBox "Please check your username or password!"
ActiveWorkbook.Close True
TextBox1.Text = ""
TextBox2.Text = ""
TextBox1.SetFocus
End Sub
When you use Sheet1.Range("A1").Value, Sheet1 is actually the Worksheet.CodeName property, read here on MSDN.
While I think you meant to use the worksheet, which name is "Sheet1", then you need to use Worksheets("Sheet1").Range("A1").Value.
If you would have defined and set your Worksheet object, you would have been able to track it.
I am using the piece of code below, to verify that no one has changed my sheet's name (or deleted it).
Option Explicit
' list of worksheet names inside Workbook - easy to modify here later
Const ShtName As String = "Sheet1"
'====================================================================
Sub VerifySheetObject()
Dim Sht As Worksheet
On Error Resume Next
Set Sht = ThisWorkbook.Worksheets(ShtName)
On Error GoTo 0
If Sht Is Nothing Then ' in case someone renamed the Sheet (or it doesn't exist)
MsgBox "Sheet has been renamed, it should be " & Chr(34) & ShtName & Chr(34), vbCritical
Exit Sub
End If
' your line here
Sht.Range("A1").Value = Date & " " & Time
End Sub
To use Variables for your Sheets use:
Dim sht as Worksheet
Set sht = Worksheets("Name")
If you are refering a lot to worksheets its a must to use, but also makes it much easier to change later on.

Resources