Automating MailMerge to create MailingLabels - excel

I am trying to create mailinglabels using mailmerge but automated from my excel file.
Basically, I already have a template saved down as a word doc. My macro populates the worksheet called "Box" with the data needed on the label. Once populated, it calls on another sub to initiate the MailMerge Procedure. My code is breaking right at the start of the MailMerge.
here is my code:
Option Explicit
Sub CreateBox()
Dim LastRow As Long
Dim N As Integer
Dim nLastRow As Long
Dim nFirstRow As Long
Dim r As Range
LastRow = Track.Range("A" & Rows.Count).End(xlUp).Row
Set r = Track.UsedRange
nFirstRow = 2
Dim i As Long: i = 2
With ActiveSheet
On Error Resume Next
Application.ScreenUpdating = False
For N = nFirstRow To LastRow
If .Cells(N, "X") = "N" Then
.Cells(N, "B").Copy
Worksheets("Box").Cells(i, "A").PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
.Cells(N, "X").Value = "Y"
.Cells(N, "D").Copy
Worksheets("Box").Cells(i, "B").PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
.Cells(N, "F").Copy
Worksheets("Box").Cells(i, "C").PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
.Cells(N, "E").Copy
Worksheets("Box").Cells(i, "D").PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
.Cells(N, "A").Copy
Worksheets("Box").Cells(i, "E").PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
.Cells(N, "T").Copy
Worksheets("Box").Cells(i, "F").PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
i = i + 1
End If
Next
End With
Call mbrMailMerge
End Sub
Sub mbrMailMerge()
Dim Sheet As Worksheet, wsName As String, N As Long, dataSrc As String
Dim wdApp As New Word.Application, wdDoc As Word.Document
dataSrc = ActiveWorkbook.FullName
Const hDir As String = "C:\Users\nparker\Documents\Personal - NML\VLS" 'update filepath
wdApp.DisplayAlerts = wdAlertsNone
For N = 2 To Sheets.Count
wsName = Box.Name
Select Case wsName
Case "Box"
Set wdDoc = wdApp.Documents.Open(hDir & dataSrc & wsName & ".docx", AddToRecentFiles:=False)
Call Mailmerge(wdDoc, dataSrc, wsName)
Case Else
MsgBox "Could not find " & wsName & " Member Word Doc for Mail Merge. Please complete manually.", vbExclamation
End Select
Next
wdApp.DisplayAlerts = wdAlertsAll
wdApp.Visible = True
Set wdDoc = Nothing: Set wdApp = Nothing
End Sub
Sub Mailmerge(wdDoc As Word.Document, dataSrc As String, wsName As String)
dataSrc = ActiveWorkbook.FullName
With wdDoc
With .Mailmerge
.MainDocumentType = wdMailingLabels
.OpenDataSource Name:=dataSrc, ConfirmConversions:=False, ReadOnly:=False, LinkToSource:=True, _
AddToRecentFiles:=False, PasswordDocument:="", PasswordTemplate:="", WritePasswordDocument:="", _
WritePasswordTemplate:="", Revert:=False, Format:=wdOpenFormatAuto, SubType:=wdMergeSubTypeAccess, _
Connection:="Provider=Microsoft.ACE.OLEDB.12.0;User ID=Admin;Data Source=dataSrc;Mode=Read;" & _
"Extended Properties=""HDR=YES;IME", SQLStatement:="SELECT * FROM `" & wsName & "$`", SQLStatement1:=""
With .DataSource
.FirstRecord = wdDefaultFirstRecord
.LastRecord = wdDefaultLastRecord
End With
.Execute Pause:=False
.Destination = wdSendToNewDocument
End With
.Close SaveChanges:=False
End With
End Sub
.
With wdDoc
With .Mailmerge '<-----my code is breaking on this line
I am expecting the macro to open the specified word doc and import the data from the worksheet "box" but instead i get a
Run time error '91': Object variable or with block variable not set error

i found a better way of doing this, it is as follows:
Sub LabelMerge()
Dim oWord As Word.Application, oDoc As Word.Document
Dim sPath As String, I As Integer, oHeaders As Range
Dim LastCol As Long
Application.ScreenUpdating = False
LastCol = Rear.Cells(1, Columns.Count).End(xlToLeft).Column
Set oHeaders = Rear.Range(Rear.Cells(1, 1), Rear.Cells(1, LastCol))
sPath = ThisWorkbook.FullName
Set oWord = CreateObject("Word.Application")
Set oDoc = oWord.Documents.Add
oWord.Visible = True
oDoc.Mailmerge.MainDocumentType = wdMailingLabels
oWord.MailingLabel.CreateNewDocumentByID LabelID:="1359804772", _
Address:="", AutoText:="ToolsCreateLabels1", LaserTray:= _
wdPrinterManualFeed, ExtractAddress:=False, PrintEPostageLabel:=False, _
Vertical:=False
oDoc.Activate
With oDoc.Mailmerge.Fields
For I = 1 To oHeaders.Columns.Count
If oHeaders.Cells(1, I).Text = "Harvest Date 1" Then
oWord.Selection.TypeText Text:="H: "
.Add oWord.Selection.Range, Name:="Harvest_Date_1"
oWord.Selection.TypeText Text:=" J: "
ElseIf oHeaders.Cells(1, I).Text = "Julian Date 1:" Then
.Add oWord.Selection.Range, Name:="Julian_Date_1"
oWord.Selection.TypeParagraph
oWord.Selection.TypeText Text:="P: "
ElseIf oHeaders.Cells(1, I).Text = "Package Date" Then
.Add oWord.Selection.Range, Name:="Package_Date"
oWord.Selection.TypeText Text:=" T: "
ElseIf oHeaders.Cells(1, I).Text = "Team" Then
.Add oWord.Selection.Range, Name:="Team"
oWord.Selection.TypeParagraph
oWord.Selection.TypeText Text:="CBI ITEM CODE: "
ElseIf oHeaders.Cells(1, I).Text = "Product Code:" Then
.Add oWord.Selection.Range, Name:="Product_Code"
End If
oWord.Selection.TypeText " "
Next I
oWord.Selection.WholeStory
oWord.Selection.ParagraphFormat.LineSpacing = LinesToPoints(33008)
End With
oDoc.Mailmerge.OpenDataSource sPath
oWord.WordBasic.mailmergepropagatelabel
oDoc.Mailmerge.ViewMailMergeFieldCodes = False
oDoc.ActiveWindow.View.ShowFieldCodes = False
Set oDoc = Nothing
Set oWord = Nothing
Application.ScreenUpdating = True
End Sub
however, this code still requires the user to pick the sheet within the datasource. Is there another way of choosing the sheet in the code so the user doesn't have to be involved at all? Specifically this line:
oDoc.Mailmerge.OpenDataSource sPath

Related

Sends all due dates in one email with copying due date rows data

Hi i have code below whereby i fond it from the net and it is similar to what i would like to do. basically i have a due dates on column "J" and i want all due dates from 90 days ahead.
when it is found due dates which 90 days plus, it will copy each row which has due dates of 90 days plus on to temp sheet and continue to look for all due dates until there isn't any and then it will send email out and delete the temp sheet back to normal.
below code currently not working and i wonder if someone could help me.
Sub Send_Table_autofilter_2()
Dim MailBody As Range
Dim dwn As Range
'If filtered remove filter. Throws error if not filtered
ActiveSheet.Range("j1").Activate
On Error Resume Next
ActiveSheet.ShowAllData
Set mWs = Worksheets("Full")
'If MailBody sheet already exists then delete it
If WorksheetExists("MailBody") Then
Application.DisplayAlerts = False
Worksheets("MailBody").Delete
Application.DisplayAlerts = True
End If
'Add a sheet to copy all todays date rows to
Sheets.Add(After:=Sheets(Sheets.Count)).Name = "MailBody"
'Return to the mail content sheet
mWs.Activate
'Set range as column A to check for todays date. If yes is found skip filter and mail creation
Set Rng = Range(Range("j1"), Range("j" & Rows.Count).End(xlUp))
For Each cell In Rng
If cell.Value <= Date + 90 Then
If Not cell.Offset(0, 1).Value = "yes" Then
'https://stackoverflow.com/questions/52340156/excel-macro-to-filter-column-to-todays-date
With Worksheets("Sheet5")
With Rng
.AutoFilter field:=1, Criteria1:=xlFilterDate, Operator:=xlFilterDynamic
End With
End With
'Copy the autofilter range and header to the MailBody sheet
Worksheets("Sheet5").AutoFilter.Range.Offset(0, 0).Copy Sheets("MailBody").Range("A1")
'Need to add yes to each autofiltered row and date. Column A is the range so offset by 10 and 11 columns.
For Each dwn In Rng.SpecialCells(xlCellTypeVisible)
Rng.Offset(0, 1).Value = "yes"
Rng.Offset(0, 2).Value = Date
Next
ActiveSheet.Range("j2").Activate
ActiveSheet.ShowAllData
' Change the following as needed
sSendTo = "JoeBloggs#yahoo.co.uk"
sSendCC = ""
sSendBCC = ""
sSubject = "Due date reached"
MsgStr = sTemp = "Hello!" & "<br><br>"
sTemp = sTemp & "The due date has been reached "
sTemp = sTemp & "for this project:" & "<br><br>"
' Assumes project name is in column B
sTemp = sTemp & " " & Cells(lRow, 2)
sTemp = sTemp & "Please take the appropriate"
sTemp = sTemp & "action." & "<br><br>"
sTemp = sTemp & "Thank you!" & "<br>"
'Set Range on MailBody Sheet, then autofit it before copying to mail
With Worksheets("MailBody")
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
Set MailBody = .Range(.Cells(1, 2), .Cells(lRow, 7)) 'Columns 2 to 7
'Set MailBody = .Range(.Cells(1, 1), .Cells(lRow, 10)) - Columns 1 to 10
End With
MailBody.Columns.AutoFit
'Create mail
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
With OutMail
.To = sSendTo
.CC = sSendCC
.Subject = sSubject
.HTMLBody = sTemp & RangetoHTML(MailBody)
.Display
'send
End With
End If
End If
MailTo = ""
MailSubject = ""
Next
'Delete MailBody sheet
Application.DisplayAlerts = False
' Worksheets("MailBody").Delete
Application.DisplayAlerts = True
End Sub
Function RangetoHTML(Rng As Range) ' Changed by Ron de Bruin 28-Oct-2006
' Working in Office 2000-2016
'Function from - https://www.rondebruin.nl/win/s1/outlook/bmail2.htm
Dim fso As Object
Dim ts As Object
Dim TempFile As String
Dim TempWB As Workbook
TempFile = Environ$("temp") & "\" & Format(Now, "dd-mm-yy h-mm-ss") & ".htm"
'Copy the range and create a new workbook to past the data in
Rng.Copy
Set TempWB = Workbooks.Add(1)
With TempWB.Sheets(1)
.Cells(1).PasteSpecial Paste:=8
.Cells(1).PasteSpecial -4163, , False, False
.Cells(1).PasteSpecial -4122, , False, False
.Cells(1).Select
Application.CutCopyMode = False
On Error Resume Next
.DrawingObjects.Visible = True
.DrawingObjects.Delete
On Error GoTo 0
End With
'Publish the sheet to a htm file
With TempWB.PublishObjects.Add( _
SourceType:=4, _
Filename:=TempFile, _
Sheet:=TempWB.Sheets(1).Name, _
Source:=TempWB.Sheets(1).UsedRange.Address, _
HtmlType:=0)
.Publish (True)
End With
'Read all data from the htm file into RangetoHTML
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.GetFile(TempFile).OpenAsTextStream(1, -2)
RangetoHTML = ts.readall
ts.Close
RangetoHTML = Replace(RangetoHTML, "align=center x:publishsource=", _
"align=left x:publishsource=")
'Close TempWB
TempWB.Close savechanges:=False
'Delete the htm file we used in this function
Kill TempFile
Set ts = Nothing
Set fso = Nothing
Set TempWB = Nothing
End Function
'Does the worksheet exists
Function WorksheetExists(WSName) As Boolean
On Error Resume Next
WorksheetExists = Worksheets(WSName).Name = WSName
On Error GoTo 0
End Function
Unless you have a very large data set (and performance is an issue) then avoid the complexity of auto-filters by just scanning down the sheet and copying each row that satisfies the criteria.
Update 1 - ignore records with missing expiry dates
Option Explicit
Sub Send_Table_autofilter_2()
Dim wb As Workbook, ws As Worksheet, wsBody As Worksheet
Dim rng As Range, dtDue As Date, iDays As Long
Dim iLastRow As Long, iMailRow As Long, i As Long
Dim sDates As String, dtTimestamp As Date, sStatus As String
Dim lines As New Collection
' delete existing MailBody Sheet
Set wb = ThisWorkbook
For Each ws In wb.Sheets
If ws.Name = "MailBody" Then
Application.DisplayAlerts = False
ws.Delete
Application.DisplayAlerts = True
End If
Next
' create new MailBody Sheet
Set wsBody = wb.Sheets.Add(After:=wb.Sheets(wb.Sheets.Count))
wsBody.Name = "MailBody"
' header row
Set ws = wb.Worksheets("Full")
With wsBody.Range("A1:H1")
.Value2 = ws.Range("C2:J2").Value2
.Font.Bold = True
End With
iMailRow = 1
' scan sheet for due in <= 90 days
' copy to MailBody
iLastRow = ws.Cells(Rows.Count, "J").End(xlUp).Row
For i = 3 To iLastRow ' assume row2 is header
If IsDate(ws.Cells(i, "J")) Then
dtDue = ws.Cells(i, "J")
iDays = DateDiff("d", Date, dtDue)
sStatus = ws.Cells(i, "K")
dtTimestamp = ws.Cells(i, "L")
ws.Cells(i, "X") = iDays
If iDays <= 90 And sStatus <> "Sent" Then
iMailRow = iMailRow + 1
ws.Range("C" & i & ":J" & i).Copy wsBody.Range("A" & iMailRow)
lines.Add i, CStr(i)
End If
End If
Next
' check if any records in collection
If lines.Count > 0 Then
' convert to html
sDates = Format(Date, "dd mmm yyyy") & " and " & Format(Date + 90, "dd mmm yyyy")
Call SendEmail(wsBody.UsedRange, sDates)
' record email sent
For i = 1 To lines.Count
ws.Range("K" & lines(i)) = "Sent"
ws.Range("L" & lines(i)) = Now()
Next
Else
MsgBox "No records due", vbInformation
End If
' delete temp
Application.DisplayAlerts = False
wsBody.Delete
Application.DisplayAlerts = True
End Sub
Sub SendEmail(MailBody As Range, sDates As String)
Const CSS = "<style>p{font:13px Verdana};</style>"
Dim msg As String, outApp, outMail
msg = "<p>Hello!" & "<br><br>" & _
"The following are due between " & sDates & _
"<br><br>Please take the appropriate action<br><br>Thank you!<br>"
'Create mail
Set outApp = CreateObject("Outlook.Application")
Set outMail = outApp.CreateItem(0)
With outMail
.To = "JoeBloggs#yahoo.co.uk"
.cc = "sSendCC"
.Subject = "Due in next 90 days"
.HTMLBody = CSS & msg & RangetoHTML(MailBody)
.Display
'send
End With
'outApp.Quit
'Set outApp = Nothing
End Sub
Function RangetoHTML(rng As Range) As String
Dim h As String, c As Integer, r As Long
h = "<table cellspacing=""0"" cellpadding=""5"" border=""1"" style=""font:13px Verdana"">"
For r = 1 To rng.Rows.Count
h = h & "<tr>"
For c = 1 To rng.Columns.Count
If r = 1 Then ' header
h = h & "<th bgcolor=""e0e0e0"">" & rng.Cells(1, c) & "</th>"
Else
h = h & "<td>" & rng.Cells(r, c) & "</td>"
End If
Next
h = h & "</tr>"
Next
RangetoHTML = h & "</table>"
End Function

Collect data from visible rows only applying manual filter

I created the macro below (my first time macro ever) to automatically generate PDFs in bulk, creating one per row populating a Word template with the corresponding fields. Now, I need to filter the data to generate PDFs only for the remaining visible rows, but cant figure out what lines of the code to modify to make this happen. I have read about the .SpecialCells(xlCellTypeVisible) but I have no idea where to use it nor if it is even the way to go. I would greatly appreciate some help. Thanks!
Sub PrintPrivacyPolicyDoc_EN()
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Dim objWord As Word.Application, wdDoc As Word.Document
Dim datos(0 To 1, 0 To 9) As String
Set a = Sheets(ActiveSheet.Name)
nom = ActiveWorkbook.Name
pto = InStr(nom, ".")
nomarch = Left(nom, pto - 1)
wArch = ThisWorkbook.Path & "\" & a.Range("B3").Text & ".dotx"
uf = a.Range("A" & Rows.Count).End(xlUp).Row
Set objWord = CreateObject("Word.Application")
objWord.DisplayAlerts = wdAlertsNone
objWord.Visible = True
uf = a.Range("A" & Rows.Count).End(xlUp).Row
Set objWord = CreateObject("Word.Application")
objWord.DisplayAlerts = wdAlertsNone
objWord.Visible = True
For j = 5 To uf
Set wdDoc = objWord.Documents.Open(wArch)
nomfic = nomarch & "_" & a.Cells(j, "A") & "_" & a.Range("C3").Text
rutainf = ThisWorkbook.Path & "\" & "PrivacyPolicy PDFs" & "\" & nomfic & ".pdf"
'Variables to find and text to substitute"
datos(0, 0) = "[Company_Name]"
datos(1, 0) = a.Cells(j, "B")
datos(0, 1) = "[Vat_Number]"
datos(1, 1) = a.Cells(j, "C")
datos(0, 2) = "[URL_Stay]"
datos(1, 2) = a.Cells(j, "D")
datos(0, 3) = "[Update_Date]"
datos(1, 3) = a.Cells(j, "E")
For I = 0 To UBound(datos, 2)
textobuscar = datos(0, I)
objWord.Selection.Move 6, -1
objWord.Selection.Find.Execute FindText:=textobuscar
While objWord.Selection.Find.Found = True
objWord.Selection.Text = datos(1, I) 'texto a reemplazar
objWord.Selection.Move 6, -1
objWord.Selection.Find.Execute FindText:=textobuscar
Wend
Next I
'Search for the file and delete it before saving the newest verion
If Dir(rutaInf) <> "" Then
Kill rutaInf
End If
'Save file with the designated name
wdDoc.SaveAs Filename:=rutaInf, FileFormat:=wdFormatPDF
'Close Word template without saving changes
wdDoc.Close savechanges:=False
MsgBox ("PDF files were successfully generated"), vbInformation, "NOTIFICATION"
wdDoc.Quit
End Sub
Untested:
Sub PrintPrivacyPolicyDoc_EN()
Dim objWord As Word.Application, wdDoc As Word.Document
Dim nomArch As String, uf As Long, wArch As String
Dim ws As Worksheet, j As Long, nomFic As String, rutaInf As String
Set ws = ActiveSheet
nomArch = Split(ws.Name, ".")(0)
wArch = ThisWorkbook.Path & "\" & ws.Range("B3").Text & ".dotx"
uf = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Set objWord = CreateObject("Word.Application")
objWord.DisplayAlerts = wdAlertsNone
objWord.Visible = True
For j = 5 To uf
If Not ws.Rows(j).Hidden Then 'visible rows only
Set wdDoc = objWord.Documents.Open(wArch)
nomFic = nomArch & "_" & ws.Cells(j, "A") & "_" & ws.Range("C3").Text
rutaInf = ThisWorkbook.Path & "\PrivacyPolicy PDFs\" & nomFic & ".pdf"
ReplaceAll wdDoc, "[Company_Name]", ws.Cells(j, "B")
ReplaceAll wdDoc, "[Vat_Number]", ws.Cells(j, "C")
ReplaceAll wdDoc, "[URL_Stay]", ws.Cells(j, "D")
ReplaceAll wdDoc, "[Update_Date]", ws.Cells(j, "E")
If Dir(rutaInf) <> "" Then Kill rutaInf
'Save file with the designated name
wdDoc.SaveAs Filename:=rutaInf, FileFormat:=wdFormatPDF
wdDoc.Close savechanges:=False
End If 'row not hidden
Next j
objWord.Quit 'close Word
End Sub
'Replace all instances of txtFind with txtReplace in doc
Sub ReplaceAll(doc As Word.Document, txtFind As String, txtReplace As String)
With doc.Range.Find
.Text = txtFind
.Replacement.Text = txtReplace
.Replacement.ClearFormatting
.Replacement.Font.Italic = False
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute Replace:=wdReplaceAll
End With
End Sub

A macro that calls 2 macros depending on the cell value

I have this chunk of code :
The macro that calls 2 other macros depending on the cell value is this :
Option Explicit
Function lastRow(col As Variant, Optional wks As Worksheet) As Long
If wks Is Nothing Then
Set wks = ActiveSheet
End If
lastRow = wks.Cells(wks.Rows.Count, col).End(xlUp).Row
End Function
Sub runMacros()
Dim vDat As Variant
Dim i As Long
Dim wks As Worksheet
Set wks = ActiveSheet
With wks
vDat = .Range(.Cells(1, "G"), .Cells(lastRow("G"), "G"))
End With
For i = LBound(vDat) To UBound(vDat)
If vDat(i, 1) = "First" Then
Macro3
Macro1
ElseIf vDat(i, 1) = "Second" Then
Macro3
Macro2
End If
Next i
End Sub
The first macro that is being called is this(Macro3) - it just creates a new folder if it does not exist:
Sub Macro3()
Dim Path As String
Dim Folder As String
Path = "C:\Users\" & Environ("Username") & "\Desktop\Rejection Folder\"
Folder = Dir(Path, vbDirectory)
If Folder = vbNullString Then
MkDir "C:\Users\" & Environ("Username") & "\Desktop\Rejection Folder\"
End If
End Sub
and then I have this macro:
Sub Macro1()
Application.ScreenUpdating = False
Dim StrMMSrc As String, StrMMDoc As String, StrMMPath As String, StrName As String, SavePath As String, StrFileName As String, MailSubjectName As String
Dim i As Long, j As Long
Const StrNoChr As String = """*./\:?|"
Dim wdApp As New Word.Application, wdDoc As Word.Document
wdApp.Visible = False
wdApp.DisplayAlerts = wdAlertsNone
StrMMSrc = ThisWorkbook.FullName
SavePath = "C:\Users\" & Environ("Username") & "\Desktop\Rejection Folder\" 'Name of the folder
StrMMPath = ThisWorkbook.Path & "\"
StrMMDoc = StrMMPath & "RejectionLetterEmployee.docx" 'Name of the word file
Set wdDoc = wdApp.Documents.Open(Filename:=StrMMDoc, AddToRecentFiles:=False, ReadOnly:=True, Visible:=False)
With wdDoc
With .MailMerge
.MainDocumentType = wdFormLetters
.OpenDataSource Name:=StrMMSrc, ReadOnly:=True, AddToRecentFiles:=False, _
LinkToSource:=False, Connection:="Provider=Microsoft.ACE.OLEDB.12.0;User ID=Admin;" & _
"Data Source=StrMMSrc;Mode=Read;Extended Properties=""HDR=YES;IMEX=1"";", _
SQLStatement:="SELECT * FROM `Rejection$`"
For i = 1 To .DataSource.RecordCount
.Destination = wdSendToNewDocument
.SuppressBlankLines = True
With .DataSource
.FirstRecord = i
.LastRecord = i
.ActiveRecord = i
If Trim(.DataFields("Name")) = "" Then Exit For
StrName = .DataFields("Name") 'File name will be determined by this column name
MailSubjectName = .DataFields("ID")
End With
.Execute Pause:=False
For j = 1 To Len(StrNoChr)
StrName = Replace(StrName, Mid(StrNoChr, j, 1), "_")
MailSubjectName = Replace(MailSubjectName, Mid(StrNoChr, j, 1), "_")
Next
StrName = Trim(StrName)
StrFileName = "C:\Users\" & Environ("Username") & "\Desktop\Rejection Folder\" & StrName
With wdApp.ActiveDocument
'.SaveAs Filename:=StrMMPath & StrName & ".docx", FileFormat:=wdFormatXMLDocument, AddToRecentFiles:=False 'Save as WORD file(not needed at the moment)
' and/or:
'.SaveAs Filename:=StrMMPath & StrName & ".pdf", FileFormat:=wdFormatPDF, AddToRecentFiles:=False 'Save to the folder where the excel sheet exists(not needed)
.SaveAs Filename:=SavePath & StrName & ".pdf", FileFormat:=wdFormatPDF, AddToRecentFiles:=False 'Save to the folder that has been created by Path_Exists function
.Close SaveChanges:=False
' Set OutApp = CreateObject("Outlook.Application")
' Set OutMail = OutApp.CreateItem(0)
' On Error Resume Next
' With OutMail
' .To = ""
' .SentOnBehalfOfName = ""
' .CC = ""
' .BCC = ""
' .Subject = "ID" & " " & MailSubjectName & " " & StrName
' .BoDy = ""
' .Attachments.Add StrFileName & ".pdf"
' .Display
'.Send
' End With
' On Error GoTo 0
' Set OutMail = Nothing
' Set OutApp = Nothing
End With
' Next i
.MainDocumentType = wdNotAMergeDocument
End With
.Close SaveChanges:=False
End With
wdApp.DisplayAlerts = wdAlertsAll
wdApp.Quit
Set wdDoc = Nothing: Set wdApp = Nothing
Application.ScreenUpdating = False
End Sub
Macro1 and Macro2 are the same code but they use a different Word file to create the PDF - Macro1 runs if a cell in "G" column contains the string "first" and Macro2 runs if it contains "second".
The macros create a PDF file and sends it via Outlook.
The problem with Macro1 and Macro2 is that they have a For loop which runs through all rows which basically contradicts what I want to do based on a cell value.
I tried to tweak it a little but since im not familiar that much with VBA I couldnt make it run on the row based on the For loop that runMacros() executes when it calls the 2 other macros.
I only succeeded making it work only on the first row or the last row.
So my question is this : How would I fix Macro1 code to work on a row that runMacros() check.
For example : runMacros() is executed via button.
it checks if G2 cell contains either "first" or "second".
if it contains "first" it will run Macro3 and Macro1.
if it contains "second" it will run Macro3 and Macro2.
runMacros() will then go to the next row, check and execute the macros until it reaches an empty row.
currently Macro1 and Macro2 have a for loop which is wrong because if the G2 contains "first" and G3 contains "second" all the PDF files will be according to Macro2 because it just replaced what Macro1 did
I want Macro1 and Macro2 to follow the row that runMacros() is checking and only execute on that row.
How do I do that?
In answering your question in passing parameters, there are a couple ways to do this. In the first example, create your vDat variable as a Range, then loop over the range and pass a range parameter.
Sub runMacros()
Dim wks As Worksheet
Set wks = ActiveSheet
Dim vDat As Range
With wks
Set vDat = .Range("G1").Resize(lastRow("G"), 1)
End With
Dim i As Long
For i = 1 To vDat.Rows.Count
If vDat.Offset(i, 0).Value = "First" Then
Macro3 vDat.Rows(i)
Macro1 vDat.Rows(i)
ElseIf vDat.Offset(i, 0).Value = "Second" Then
Macro3 vDat.Rows(i)
Macro2 vDat.Rows(i)
End If
Next i
End Sub
Private Sub Macro1(ByRef theRow As Range)
Debug.Print "Macro1 row address = " & theRow.Address
End Sub
Private Sub Macro2(ByRef theRow As Range)
Debug.Print "Macro2 row address = " & theRow.Address
End Sub
Private Sub Macro3(ByRef theRow As Range)
Debug.Print "Macro3 row address = " & theRow.Address
End Sub
But you actually created vDat as an array, so you can just pass the value of that row in the array:
Sub runMacros()
Dim wks As Worksheet
Set wks = ActiveSheet
Dim vDat As Variant
With wks
vDat = .Range("G1").Resize(lastRow("G"), 1).Value
End With
Dim i As Long
For i = LBound(vDat, 1) To UBound(vDat, 1)
If vDat(i, 0) = "First" Then
Macro3 vDat(i, 0)
Macro1 vDat(i, 0)
ElseIf vDat(i, 0) = "Second" Then
Macro3 vDat(i, 0)
Macro2 vDat(i, 0)
End If
Next i
End Sub
Private Sub Macro1(ByVal theRowValue As Variant)
Debug.Print "Macro1 row value = " & theRowValue
End Sub
Private Sub Macro2(ByVal theRowValue As Variant)
Debug.Print "Macro2 row value = " & theRowValue
End Sub
Private Sub Macro3(ByVal theRowValue As Variant)
Debug.Print "Macro3 row value = " & theRowValue
End Sub
What is not clear in your code and question is how the row relates to the DataSource or how you are using it in Macro1 or Macro2. I would also suggest renaming your macros to something more descriptive to what action the macro is performing.
With MailMerge you can create a batch of documents from a datasource.
Using the Status column as a WHERE clause in the datasource SQL allows you to create the
documents with only 2 runs of the same subroutine using a parameter to apply the different template.
Option Explicit
Sub runMacros()
Dim Template1 As String, Template2 As String, Path As String, Folder As String
Template1 = ThisWorkbook.Path & "RejectionLetterEmployee.docx"
Template2 = ThisWorkbook.Path & "RejectionLetterEntrepreneur.docx"
' create path for documents
Path = "C:\Users\" & Environ("Username") & "\Desktop\Rejection Folder\"
Folder = Dir(Path, vbDirectory)
If Folder = vbNullString Then
MkDir Path
End If
' create documents
CreateDocuments "First", Template1, Path
CreateDocuments "Second", Template2, Path
MsgBox "Ended"
End sub
Sub CreateDocuments(Status As String, Template As String, SavePath)
MsgBox "Running macro for Status = [" & Status & "] using " & Template & vbCrLf & _
" into Folder " & SavePath, vbInformation
Const StrNoChr As String = """*./\:?|"
' Paths and Filename
Dim strMMSrc As String, strMMDoc As String, strMMPath As String
Dim StrFileName As String, t0 As Single
t0 = Timer
' open template
Dim wdApp As New Word.Application, wdDoc As Word.Document, i As Integer, j As Integer
Dim strName, MailSubjectName
wdApp.Visible = False
wdApp.DisplayAlerts = wdAlertsNone
Set wdDoc = wdApp.Documents.Open( _
Filename:=Template, _
AddToRecentFiles:=False, _
ReadOnly:=True, _
Visible:=False)
strMMSrc = ThisWorkbook.FullName ' datasource name
With wdDoc.MailMerge
.MainDocumentType = wdFormLetters
.Destination = wdSendToNewDocument
.SuppressBlankLines = True
.OpenDataSource Name:=strMMSrc, ReadOnly:=True, AddToRecentFiles:=False, _
LinkToSource:=False, Connection:="Provider=Microsoft.ACE.OLEDB.12.0;User ID=Admin;" & _
"Data Source=strMMSrc;Mode=Read;Extended Properties=""HDR=YES;IMEX=1"";", _
SQLStatement:=" SELECT * FROM `Rejection$` WHERE Status = '" & Status & "'"
' confirm to create docs
If vbNo = MsgBox(.DataSource.RecordCount & " documents will be created in " & SavePath & _
", continue ?", vbYesNo, "Confirm") Then
GoTo skip
End If
' create one doc for each record in datasource
For i = 1 To .DataSource.RecordCount
With .DataSource
.FirstRecord = i
.LastRecord = i
.ActiveRecord = i
strName = Trim(.DataFields("Name"))
MailSubjectName = Trim(.DataFields("ID"))
'Debug.Print "Raw", i, strName, MailSubjectName
If strName = "" Then Exit For
End With
' do merge
.Execute Pause:=False
' construct doc filename to save
' replace illegal characters
For j = 1 To Len(StrNoChr)
strName = Replace(strName, Mid(StrNoChr, j, 1), "_")
MailSubjectName = Replace(MailSubjectName, Mid(StrNoChr, j, 1), "_")
Next
Debug.Print "Cleaned ", i, strName, MailSubjectName
'Save to the folder that has been created by Path_Exists function
StrFileName = SavePath & strName
With wdApp.ActiveDocument
.SaveAs Filename:=SavePath & strName & ".pdf", _
FileFormat:=wdFormatPDF, AddToRecentFiles:=False
.Close SaveChanges:=False
End With
Next i
.MainDocumentType = wdNotAMergeDocument
End With
MsgBox i - 1 & " documents created in " & SavePath, vbInformation, "Completed in " & Int(Timer - t0) & " secs"
skip:
' cleanup
wdDoc.Close SaveChanges:=False
wdApp.DisplayAlerts = wdAlertsAll
wdApp.Quit
Set wdDoc = Nothing
Set wdApp = Nothing
End Sub

Save Excel mailmerge to PDF only on the last row with data

I have been using a code which uses mailmerge from the excel sheet to my word template and then proceeds to save all the rows in my excel sheet to PDF(in the word template of course). - The code works perfectly fine.
The code was taken from this forum :
https://www.msofficeforums.com/mail-merge/21803-mailmerge-tips-tricks.html
How my Excel sheet looks like :
A1-E1 = Headers
A2-E2 = data
A3-E3 = data
A4-E4 = data
and so on...
How the code currently works :
The code saves all rows of data from the excel sheet into my word template(with mailmerge) and then into PDFs.
My goal :
I want to change the code so it only saves the last row of data in the excel sheet into my word template(with mailmerge) and then into to PDF.
Sub RunMerge()
' Sourced from: https://www.msofficeforums.com/mail-merge/21803-mailmerge-tips-tricks.html
' Note: this code requires a reference to the Word object model to be set, via Tools|References in the VBE.
Application.ScreenUpdating = False
Dim StrMMSrc As String, StrMMDoc As String, StrMMPath As String, StrName As String
Dim i As Long, j As Long
Const StrNoChr As String = """*./\:?|"
Dim wdApp As New Word.Application, wdDoc As Word.Document
wdApp.Visible = False
wdApp.DisplayAlerts = wdAlertsNone
StrMMSrc = ThisWorkbook.FullName
StrMMPath = ThisWorkbook.Path & "\"
StrMMDoc = StrMMPath & "MailMergeDocument.doc"
Set wdDoc = wdApp.Documents.Open(Filename:=StrMMDoc, AddToRecentFiles:=False, ReadOnly:=True, Visible:=False)
With wdDoc
With .MailMerge
.MainDocumentType = wdFormLetters
.OpenDataSource Name:=StrMMSrc, ReadOnly:=True, AddToRecentFiles:=False, _
LinkToSource:=False, Connection:="Provider=Microsoft.ACE.OLEDB.12.0;User ID=Admin;" & _
"Data Source=StrMMSrc;Mode=Read;Extended Properties=""HDR=YES;IMEX=1"";", _
SQLStatement:="SELECT * FROM `Sheet1$`"
For i = 1 To .DataSource.RecordCount
.Destination = wdSendToNewDocument
.SuppressBlankLines = True
With .DataSource
.FirstRecord = i
.LastRecord = i
.ActiveRecord = i
If Trim(.DataFields("Name")) = "" Then Exit For
StrName = .DataFields("Name")
End With
.Execute Pause:=False
For j = 1 To Len(StrNoChr)
StrName = Replace(StrName, Mid(StrNoChr, j, 1), "_")
Next
StrName = Trim(StrName)
With wdApp.ActiveDocument
'Add the name to the footer
'.Sections(1).Footers(wdHeaderFooterPrimary).Range.InsertBefore StrName
'.SaveAs Filename:=StrMMPath & StrName & ".docx", FileFormat:=wdFormatXMLDocument, AddToRecentFiles:=False
' and/or:
.SaveAs Filename:=StrMMPath & StrName & ".pdf", FileFormat:=wdFormatPDF, AddToRecentFiles:=False
.Close SaveChanges:=False
End With
Next i
.MainDocumentType = wdNotAMergeDocument
End With
.Close SaveChanges:=False
End With
wdApp.DisplayAlerts = wdAlertsAll
wdApp.Quit
Set wdDoc = Nothing: Set wdApp = Nothing
Application.ScreenUpdating = False
End Sub
Thanks in advance.
Replace:
For i = 1 To .DataSource.RecordCount
with:
i = .DataSource.RecordCount
or, if there are rows in use below that in other columns:
i = ActiveSheet.Range("A" & ActiveSheet.Rows.Count).End(xlUp).Row - 1
where 'A' is any column not containing data below the last record and delete both:
If Trim(.DataFields("Name")) = "" Then Exit For
and:
Next i

VBA Mailmerge to pdf Output

Good Morning
I have modified the code from this post: Automating Mail Merge using Excel VBA
But I only want pdf output but as soon as I take out the word code, it baulks. I think the problem is that if I don't save it as word, it doesn't shut the template down properly (there is code to close it). I have to manually click "Don't Save" and then it chokes as it tries to reopen the file for the next line. Any idea how to get around that? - Any help much appreciated. Thanks.
Public Sub MailMergeCert()
Dim bCreatedWordInstance As Boolean
Dim objWord As Word.Application
Dim objMMMD As Word.Document
Dim FirstName As String
Dim LastName As String
Dim Training As String
Dim SeminarDate As String
Dim HoursComp As String
Dim Location As String
Dim Objectives As String
Dim Trainer As String
Dim r As Long
Dim ThisFileName As String
'Your Sheet names need to be correct in here
Set sh1 = Sheets("Periop")
lastrow = Sheets("Periop").Range("A" & Rows.Count).End(xlUp).Row
r = 2
For r = 2 To lastrow
If IsEmpty(Cells(r, 10).Value) = False Then GoTo nextrow
FirstName = sh1.Cells(r, 1).Value
LastName = sh1.Cells(r, 2).Value
Training = sh1.Cells(r, 3).Value
SeminarDate = sh1.Cells(r, 4).Value
HoursComp = sh1.Cells(r, 5).Value
Location = sh1.Cells(r, 6).Value
Objectives = sh1.Cells(r, 7).Value
Trainer = sh1.Cells(r, 8).Value
SeminarDate = Format(sh1.Cells(r, 4).Value, "d mmmm YYYY")
' Setup filenames
Const WTempName = "Certificate_Periop_2016.docx" 'Template name
' Setup directories
cDir = ActiveWorkbook.Path + "\" 'Change if required
ThisFileName = ThisWorkbook.Name
On Error Resume Next
' Create a Word Application instance
bCreatedWordInstance = False
Set objWord = GetObject(, "Word.Application")
If objWord Is Nothing Then
Err.Clear
Set objWord = CreateObject("Word.Application")
bCreatedWordInstance = True
End If
If objWord Is Nothing Then
MsgBox "Could not start Word"
Err.Clear
On Error GoTo 0
Exit Sub
End If
' Let Word trap the errors
On Error GoTo 0
' Set to True if you want to see the Word Doc flash past during construction
objWord.Visible = False
'Open Word Template
Set objMMMD = objWord.Documents.Open(cDir + WTempName)
objMMMD.Activate
'Merge the data
With objMMMD
.MailMerge.OpenDataSource Name:=cDir + ThisFileName, sqlstatement:="SELECT * FROM `Periop$`" ' Set this as required
With objMMMD.MailMerge 'With ActiveDocument.MailMerge
.Destination = wdSendToNewDocument
.SuppressBlankLines = True
With .DataSource
.FirstRecord = r - 1
.LastRecord = r - 1
.ActiveRecord = r - 1
' EmployeeName = .EmployeeName
End With
.Execute Pause:=False
End With
End With
' Save new file
'Path and YYMM
Dim PeriopCertPath As String
PeriopCertPath = "C:\Users\305015724\Documents\ApplicationsTraining\2016\Periop\"
Dim YYMM As String
YYMM = Format(sh1.Cells(r, 11).Value, "YYMM")
'Word document
Dim NewFileNameWd As String
NewFileNameWd = YYMM & "_" & sh1.Cells(r, 12).Value & "_" & sh1.Cells(r, 2).Value & ".docx" 'Change File Name as req'd"
objWord.ActiveDocument.SaveAs Filename:=PeriopCertPath & NewFileNameWd
'PDF
Dim NewFileNamePDF As String
NewFileNamePDF = YYMM & "_" & sh1.Cells(r, 12).Value & "_" & sh1.Cells(r, 2).Value '& ".pdf" 'Change File Name as req'd"
objWord.ActiveDocument.ExportAsFixedFormat PeriopCertPath & NewFileNamePDF, ExportFormat:=wdExportFormatPDF
' Close the Mail Merge Main Document
objMMMD.Close savechanges:=wdDoNotSaveChanges
Set objMMMD = Nothing
' Close the New Mail Merged Document
If bCreatedWordInstance Then
objWord.Quit
End If
0:
Set objWord = Nothing
Cells(r, 10).Value = Date
nextrow:
Next r
End Sub
I recorded saving a workbook as a pdf and this is the output:
ActiveDocument.ExportAsFixedFormat OutputFileName:= _
"C:\Users\me\Desktop\Doc1.pdf", ExportFormat:=wdExportFormatPDF, _
OpenAfterExport:=True, OptimizeFor:=wdExportOptimizeForPrint, Range:= _
wdExportAllDocument, From:=1, To:=1, Item:=wdExportDocumentContent, _
IncludeDocProps:=True, KeepIRM:=True, CreateBookmarks:= _
wdExportCreateNoBookmarks, DocStructureTags:=True, BitmapMissingFonts:= _
True, UseISO19005_1:=False
It seems like you might try:
objWord.ActiveDocument.ExportAsFixedFormat PeriopCertPath & NewFileNamePDF,
ExportFormat:=wdExportFormatPDF, OpenAfterExport:=False
The pdf generation always worked and I think I now have the Word bit sorted as well. This is the part of the code that generates the pdf and then closes Word (and a few other things ...)
'Print Certificate
'Print required
If sh1.Cells(r, 12) = "print" Then
'remove background image
ActiveWindow.ActivePane.View.SeekView = wdSeekCurrentPageHeader
Selection.WholeStory
Selection.Cut
'Print Certificate
objWord.ActiveDocument.PrintOut
'Close the Mail Merge Main Document
objWord.ActiveDocument.Close (wdDoNotSaveChanges)
objMMMD.Close savechanges:=wdDoNotSaveChanges
Set objMMMD = Nothing
Else
'Close the Mail Merge Main Document
objWord.ActiveDocument.Close (wdDoNotSaveChanges)
objMMMD.Close savechanges:=wdDoNotSaveChanges
Set objMMMD = Nothing
End If
' Create a Word Application instance
bCreatedWordInstance = False
Set objWord = GetObject(, "Word.Application")
' Close the New Mail Merged Document
If bCreatedWordInstance Then
objWord.Quit
End If
0:
Set objWord = Nothing

Resources