Mail merge output to different folders
I found the base macro in this MS Office Forums thread. This post is to add my own usage tips; hopefully they will save you from the same trial and error I went through.
Assumed knowledge:
- How to add a VBA macro to a Word document.
- How to set up a mail merge using an Excel file as the source data.
Notes:
- The table column headers in your mail merge source data can't have brackets
()in them. I found this out by trial and error. Possibly other punctuation marks are verboten also. - In my example, the macro is pulling data from Excel columns called:
Personnel nameoutputFolderoutputFile
- You will need to change all spaces in column names to underscores
_in the macro. You don't have to do this to the Excel columns themselves; just the VBA. I found this out by trial and error. If your Excel column isZiggy ziggy zoo, the VBA needs to sayZiggy_ziggy_zoo. - The way I've set this up means that when you click on 'Edit Individual Documents', the macro runs automatically, with no option to further select records. That suits me fine, and as the macro author notes, you can achieve greater control by using the 'Edit Recipient List' tools anyway.
- This macro can save the docs as either .docx or .pdf. I'll refer to them as 'PDFs' because that's what I've chosen.
What follows is the VBA broken up with my notes. At the end the entire code is repeated for easy copy-pasting.
Sub MailMergeToDoc()
' Collects the results of the mail merge in a document
Apparently, the name of the macro is important. In the thread linked above, the macro is called Merge_To_Individual_Files, but OP states "If you rename the above macro as MailMergeToDoc, clicking on the 'Edit Individual Documents' button will intercept the merge and the process will run automatically." How does the name of the macro affect anything? I don't know.
Application.ScreenUpdating = False
Turns off the screen re-drawing with every action, which can save time when running a macro that goes through a lot of records. It needs to be set back to TRUE at the end of the macro. It seems silly that you the user have to do that, though.
Dim StrFolder As String, StrName As String, MainDoc As Document, i As Long, j As Long
Some variables, innit. We're telling the computer 'reserve some pieces of your memory for us to use, and we'll refer to them with these labels'. A String is some text, and a Long is a big number (as opposed to an Int, which 'only' goes up to 2 billion. That's probably overkill here, but whatever, I didn't write the macro).
Const StrNoChr As String = """*./\:?|"
Characters that we can't have or at least don't want in filenames.
Set MainDoc = ActiveDocument
With MainDoc
'StrFolder = .Path & "\"
'Uncomment to use macro's own folder
If you just want to save all the PDFs into the same folder as the Word doc (i.e. the one that we're running this macro from) you can uncomment this line (remove the ') and use that. The .Path tells the macro to use whatever folder it's already in. The "\" needs to be manually added because computers are so stupid and will scream and cry if you don't spoon-feed them absolutely everything.
(Why do we have to dick about with creating a Document variable ('MainDoc') and then setting it to ActiveDocument and calling that - instead of just telling the macro With ActiveDocument? It's because we had to first tell the computer to reserve some pieces of memory, and then tell it what to put there.)
With .MailMerge
.Destination = wdSendToNewDocument
.SuppressBlankLines = True
.SuppressBlankLines is a built-in command for use in MailMerge macros. I assume it acts as a failsafe in case you forgot to exclude blank entries in the 'Edit Recipient List' tools.
wdSendToNewDocument does pretty much what you'd expect.
On Error Resume Next
For i = 1 To .DataSource.RecordCount
With .DataSource
.FirstRecord = i
.LastRecord = i
.ActiveRecord = i
This is the start of a 'for' loop (tell the computer to do this thing until it can't do it any more).
If Trim(.DataFields("Personnel_name")) = "" Then Exit For
StrFolder = .DataFields("outputFolder") & "\"
If Dir(StrFolder) = "" Then MkDir StrFolder
StrName = .DataFields("outputFile")
"Personnel_name"is the name of a column in my source spreadsheet; change this to whatever is appropriate for your data. (The column is actuallyPersonnel name, but as mentioned earlier, the column names need to be written in the macro with underscores for spaces.) If the column is blank, that record gets ignored.- My spreadsheet contains a column called
outputFolderthat specifies the folder paths where I want each record to be saved. If you're not using this (because you're using theStrFolder = .Path & "\"from earlier), comment out this line by adding a'at the start. - If there's a chance that the output folder won't already exist when the macro is run, the line
If Dir(StrFolder) = "" Then MkDir StrFolderwill create it on the fly. Comment out this line (by adding a'at the start) if you won't be using this.- Weirdness alert. If the folder does exist but is empty, the macro can become 'confused' (for want of a better term) and crash at this point. It's an easy fix; delete the empty folder and run the macro again. Repeat as necessary until it behaves itself. (Alternatively, save a random file in that folder so the macro can 'see' it.)
- My spreadsheet contains a column called
outputFilethat specifies the name I want each file to have; change this to whatever is appropriate for your data. You can chain together multiple columns as per the example in the OP:StrName = .DataFields("Last_Name") & "_" & .DataFields("First_Name")(which would give a filename likeSmith_Jim).
End With
On Error GoTo NextRecord
.Execute Pause:=False
For j = 1 To Len(StrNoChr)
StrName = Replace(StrName, Mid(StrNoChr, j, 1), "_")
Anything from StrNoChr earlier gets replaced by an underscore _.
Next
StrName = Trim(StrName)
With ActiveDocument
'Add the name to the footer
'.Sections(1).Footers(wdHeaderFooterPrimary).Range.InsertBefore StrName
If you want the filename in the document footer, uncomment these lines (remove the ' at the start). I've never bothered to use it.
.SaveAs FileName:=StrFolder & StrName & ".pdf", FileFormat:=wdFormatPDF, AddToRecentFiles:=False
If you want the output files to be Word docs, change ".pdf" to ".docx" and wdFormatPDF to... wdFormatXMLDocument. Word (and Excel) files are just XML files under the bonnet! (Take a random Word/Excel file that you don't mind sacrificing, change its file extension to zip, then unzip it and see what's inside!)
If you want to save the output files as docx and PDF, duplicate the line and edit so you've gone one line with ".pdf" and wdFormatPDF and another with ".docx" and wdFormatXMLDocument.
.Close SaveChanges:=False
End With
NextRecord:
Next i
End With
End With
Application.ScreenUpdating = True
End Sub
Some gubbins to wrap everything up.
Whole thing
Sub MailMergeToDoc()
'
' MailMergeToDoc Macro
' Collects the results of the mail merge in a document
' Sourced from: https://www.msofficeforums.com/mail-merge/21803-mailmerge-tips-tricks.html
Application.ScreenUpdating = False
Dim StrFolder As String, StrName As String, MainDoc As Document, i As Long, j As Long
Const StrNoChr As String = """*./\:?|"
Set MainDoc = ActiveDocument
With MainDoc
'StrFolder = .Path & "\"
'Uncomment to use macro's own folder
With .MailMerge
.Destination = wdSendToNewDocument
.SuppressBlankLines = True
On Error Resume Next
For i = 1 To .DataSource.RecordCount
With .DataSource
.FirstRecord = i
.LastRecord = i
.ActiveRecord = i
If Trim(.DataFields("Personnel_name")) = "" Then Exit For
'StrFolder = .DataFields("outputFolder") & "\"
' If Dir(StrFolder) = "" Then MkDir StrFolder
StrName = .DataFields("outputFile")
End With
On Error GoTo NextRecord
.Execute Pause:=False
For j = 1 To Len(StrNoChr)
StrName = Replace(StrName, Mid(StrNoChr, j, 1), "_")
Next
StrName = Trim(StrName)
With ActiveDocument
'Add the name to the footer
'.Sections(1).Footers(wdHeaderFooterPrimary).Range.InsertBefore StrName
.SaveAs FileName:=StrFolder & StrName & ".pdf", FileFormat:=wdFormatPDF, AddToRecentFiles:=False
.Close SaveChanges:=False
End With
NextRecord:
Next i
End With
End With
Application.ScreenUpdating = True
End Sub