I have a Word document containing a few lines of text and a table with many place holders I will fill in from Excel later in the same document but saving it as PDF.
My final goal is to duplicate the whole text in the word file as it is before any modification and paste it every time I have to complete it with the values in the Excel file.
Inside word VBA, this works perfectly. Copy the whole document and past it at the end, duplicating the table and the lines of text.
Selection.WholeStory
Selection.Copy
Selection.MoveDown Unit:=wdParagraph, Count:=2
Selection.PasteAndFormat (wdFormatOriginalFormatting)
And in Excel VBA, I have this working just fine. Except the * Asterics part, I don't know how to execute the code that works in Word VBA from Excel VBA.
Sub GenerateDoc()
DocLoc = Application.ActiveWorkbook.Path & "\CategoryTable2.docx"
'Open Word Template
With Sheet2
On Error Resume Next 'If Word is already running
Set WordApp = GetObject("Word.Application")
If Err.Number <> 0 Then
'Launch a new instance of Word
Err.Clear
'On Error GoTo Error_Handler
Set WordApp = CreateObject("Word.Application")
End If ' Work Running Check
WordApp.Visible = True 'Make the application visible to the user
Set WordDoc = WordApp.Documents.Open(Filename:=DocLoc, ReadOnly:=False) 'Open Template
'This is not workin, no error throw however
'*****************************************
WordDoc.Content.WholeStory
WordDoc.Content.Copy
'***************************************
For component = 15 To 150
iRow = component
If .Cells(iRow, 1).Value = 0 And .Cells(iRow, 2).Text <> "" Then
For CustCol = 3 To 85 'Move Through Columns
If Left(.Cells(13, CustCol).Text, 1) = "[" And Right(.Cells(13, CustCol).Text, 1) = "]" Then
varName = .Cells(13, CustCol).Value 'Determine Variable Name
'varName = "[" & varName & "]"
VarValue = Trim(.Cells(iRow, CustCol).Text) 'Determine Variable Value
With WordDoc.Content.Find
.Text = varName
.Replacement.Text = Application.WorksheetFunction.Text(VarValue, "General")
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll 'Find & Replace all instances
End With
End If
Next CustCol
End If
Next component
'This is not working, no error throw however
'**************************************************************************
WordDoc.Content.MoveDown Unit:=wdParagraph, Count:=2
WordDoc.Content.PasteAndFormat (wdFormatOriginalFormatting)
'**************************************************************************
Filename = Application.ActiveWorkbook.Path & "\ComponentsTable.pdf" 'Create full filename & Path with current workbook location, Last Name & First Name
On Error Resume Next
Kill (Filename) 'Delete filename with the same name if it exists
On Error GoTo 0
On Error Resume Next
WordDoc.ExportAsFixedFormat OutputFileName:=Filename, ExportFormat:=wdExportFormatPDF
WordDoc.Close False
WordApp.Quit
Set WordDoc = Nothing
Set WordApp = Nothing
End With
End Sub
You need to change the logic of your approach.
'WordDoc.Content.WholeStory' specifies an object. Your code does nothing with it. WordDoc.Content.Copy copies an unrelated, other object. Perhaps you mean 'WordDoc.Content.WholeStory.Copy' but this argument is moot. Imagine the entire Word document as one string containing text as well as formatting characters. Therefore you can't copy the WholeStory which is a range. You can only copy its Text.
Once you assign the Text to a string you can paste it to a single cell in Excel. In other words, the String created in Word is understood by Excel and handled within Excel the way Excel handles its own strings. However, that string will definitely contain many characters Excel can't interpret and may contain some that Excel interprets differently. They may even cause Excel to split the original string into more than one cell.
Therefore you need to parse the string lifted from Word and manipulate it into the format you want it to have in Excel. The transition you are asking about takes place at the point where a Word-string becomes an Excel-string. Bear in mind that a Word-range can't become an Excel-range because the two are entirely different animals.
Ok, I found something interesting.
I was not getting any error message because of the
ON ERROR RESUME NEXT
I found that to avoid this, ON ERROR GOTO 0 worked. After that, it was easy to google for errors and find what was wrong. Also, my logic was flawed, I fix it like this. The *Asterix is the interesting part.
Reference https://learn.microsoft.com/en-us/office/vba/api/word.range.copy
Sub GenerateDoc()
Dim WordApp As New Word.Application
Dim WordDoc As Word.Document
DocLoc = Application.ActiveWorkbook.Path & "\CategoryTable2.docx"
'Open Word Template
With Sheet2
On Error Resume Next 'If Word is already running
Set WordApp = GetObject("Word.Application")
If Err.Number <> 0 Then
'Launch a new instance of Word
Err.Clear
'On Error GoTo Error_Handler
Set WordApp = CreateObject("Word.Application")
End If ' Work Running Check
WordApp.Visible = True 'Make the application visible to the user
Set WordDoc = WordApp.Documents.Open(Filename:=DocLoc, ReadOnly:=False) 'Open Template
On Error GoTo 0
'**********************************************************
WordDoc.Range(WordDoc.Content.Start, WordDoc.Content.End).Cut
'
' WordDoc.Content.Selection.WholeStory
' WordDoc.Content.Selection.Copy
For component = 15 To 150
iRow = component
If .Cells(iRow, 1).Value = 0 And .Cells(iRow, 2).Text <> "" Then
'Now past a template copy
'*****************************************************************************************
Set myRange = WordDoc.Range(Start:=WordDoc.Content.End - 1, End:=WordDoc.Content.End - 1)
myRange.Paste
For CustCol = 3 To 85 'Move Through Columns
If Left(.Cells(13, CustCol).Text, 1) = "[" And Right(.Cells(13, CustCol).Text, 1) = "]" Then
varName = .Cells(13, CustCol).Value 'Determine Variable Name
'varName = "[" & varName & "]"
VarValue = Trim(.Cells(iRow, CustCol).Text) 'Determine Variable Value
With WordDoc.Content.Find
.Text = varName
.Replacement.Text = Application.WorksheetFunction.Text(VarValue, "General")
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll 'Find & Replace all instances
End With
End If
Next CustCol
End If
Next component
' WordDoc.MoveDown Unit:=wdParagraph, Count:=2
' WordDoc.PasteAndFormat (wdFormatOriginalFormatting)
Filename = Application.ActiveWorkbook.Path & "\ComponentsTable.pdf" 'Create full filename & Path with current workbook location, Last Name & First Name
On Error Resume Next
Kill (Filename) 'Delete filename with the same name if it exists
On Error GoTo 0
On Error Resume Next
WordDoc.ExportAsFixedFormat OutputFileName:=Filename, ExportFormat:=wdExportFormatPDF
WordDoc.Close False
WordApp.Quit
Set WordDoc = Nothing
Set WordApp = Nothing
End With
End Sub
Related
I've written a VBA macro which resides in an Excel workbook. When run, it will open an existing Word document (which is stored in the same directory as the Excel workbook), copy some content from cells in the Excel workbook into the Word document, save the Word doc under a new name (in the same directory) and kill the original Word doc. This process works as expected on first run. But on a second run, I get a Run-time error 462. I'm sure it's due to my ignorance around creating and using application instances within VBA code (I've just begun learning). I'm using Microsoft 365 Apps for Enterprise.
Sub ExcelToWord()
Dim wordApp As Word.Application
Dim wDoc As Word.Document
Dim strFile As String
'Open Word file
strFile = ("G:\HOME\Word File.docx")
Set wordApp = CreateObject("word.Application")
Set wDoc = wordApp.Documents.Open("G:\HOME\Word File.docx")
wordApp.Visible = True
'Copy data from Excel to Word
wDoc.ContentControls(1).Range.Text = Sheets("Model").Cells(4, 2)
wDoc.ContentControls(2).Range.Text = Format(Date, "mm/dd/yyyy")
wDoc.ContentControls(3).Range.Text = Sheets("Model").Range("X4")
Word.Application.Activate
'Save Word Document with new name
ActiveDocument.SaveAs Filename:=ActiveDocument.Path & "\" & Format(Sheets("Model").Range("B14"), "YYYY") & " " & ThisWorkbook.Sheets("Model").Range("B4") & " " & Format(Date, "YYYY-mm-dd") & ".docx"
'Delete original Word document
Kill strFile
End Sub
I've researched this for hours and tried multiple solutions, including commenting out all of the Copy Data block to try and zero in on the error. But no luck. I hope I've posted this request properly. Thank you in advance for any help.
Is this what you are trying? I have commented the code but if you face any issues, simply ask. What you have is Early Binding. I have used Late Binding so that you do not need to add any references to the MS Word application.
Option Explicit
Private Const wdFormatXMLDocument As Integer = 12
Sub ExcelToWord()
Dim oWordApp As Object, oWordDoc As Object
Dim FlName As String
Dim FilePath As String
Dim NewFileName As String
'~~> This is the original word file. Change as applicable
FlName = "G:\HOME\Word File.docx"
'~~> Check if word file exists
If Dir(FlName) = "" Then
MsgBox "Word File Not Found"
Exit Sub
End If
'~~> Establish an Word application object if open
On Error Resume Next
Set oWordApp = GetObject(, "Word.Application")
'~~> If not open then create a new word application instance
If Err.Number <> 0 Then
Set oWordApp = CreateObject("Word.Application")
End If
Err.Clear
On Error GoTo 0
oWordApp.Visible = True
Set oWordDoc = oWordApp.Documents.Open(FlName)
With oWordDoc
'~~> File path
FilePath = .Path & "\"
'~~> New File name
NewFileName = FilePath & _
Format(ThisWorkbook.Sheets("Model").Range("B14").Value, "YYYY") & _
" " & _
ThisWorkbook.Sheets("Model").Range("B4").Value & _
" " & _
Format(Date, "YYYY-mm-dd") & ".docx"
'~~> Copy data from Excel to Word
.ContentControls(1).Range.Text = Sheets("Model").Cells(4, 2).Value2
.ContentControls(2).Range.Text = Format(Date, "mm/dd/yyyy")
.ContentControls(3).Range.Text = Sheets("Model").Range("X4").Value2
'~~> Save the word document
.SaveAs Filename:=NewFileName, FileFormat:=wdFormatXMLDocument
DoEvents
End With
'~~> Delete original Word document
Kill FlName
End Sub
The goal is to replace the last line of a document. The last line always starts with a $. I am using a find here but don't have to, I couldn't get any other way to function.
I have used replace.text successfully within find but due to some conditions I can't use that without tons of if statements running different Finds.
Everything seems to be working I try except for going to the end of the page or expanding to delete/replace the entire line after the $.
Specifically the .Expand function doesn't work for me the 10 different ways I've tried. and .EndKeys never works for me. (I have tried every combination of WordDoc.Expand WordApp.Expand WordSelection.Expand setting activedocument etc.)
The code is a bit of a mess at this point. The For Each oRange is the only part with issues and is my 10th iteration of trying to get it to work by now.
Sub OpenDoc()
Dim strFile As String
Dim strPN As String
Dim WordApp As Object, WordDoc As Object
Dim WordSelection As Object
Const wdReplaceOne = 1
Dim oRange As Object
Dim RoundPrice As Integer
Dim PriceString As String
Range("A1").Select
Range(Selection, Selection.End(xlToRight)).Select
Range(Selection, Selection.End(xlDown)).Select
For i = Selection.Rows.Count To 1 Step -1
If Cells(i, 4).Value <> "" Then
If Cells(i, 4).Value <> "PN" Then
'Print SD
If IsError(Cells(i, 28).Value) = True Then
RoundPrice = Cells(i, 12).Value
RoundPrice = RoundPrice * 0.85
PriceString = RoundPrice
PriceString = Left(PriceString, Len(PriceString) - 1)
PriceString = PriceString & "9"
strPN = Cells(i, 4).Value
strFile = "c:\Users\Robert\Desktop\Masterlist\B_" & strPN & ".docx" 'change to path of your file
If Dir(strFile) <> "" Then 'First we check if document exists at all at given location
'Word session creation
Set WordApp = CreateObject("Word.Application")
'word will be closed while running
WordApp.Visible = True
'open the .doc file
Set WordDoc = WordApp.Documents.Open(strFile)
'WordDoc.PrintOut
Set WordSelection = WordApp.Selection
For Each oRange In WordDoc.StoryRanges
With oRange.Find
.Forward = False
.Text = "$"
.Execute Find
End With
With WordSelection
.Expand Unit:=wdLine
.Text = "$" & PriceString
End With
Next oRange
WordDoc.SaveAs ("c:\Users\Robert\Desktop\B" & "_" & strPN & ".docx")
WordApp.Quit
End If
End If
End If
End If
Next i
End Sub
Your code isn't working because WordSelection doesn't get moved after the Set statement. You can accomplish what you need just using the Range. You should also check that Find actually found something.
For Each oRange In WordDoc.StoryRanges
With oRange
With .Find
.Forward = False
.Text = "$"
End With
If .Find.Execute Then
.Expand Unit:=wdParagraph
.Text = "$" & PriceString
End If
End With
Next oRange
You should also get rid of the late binding as you gain nothing at all by using it. Instead set a reference to the Word object library and declare the objects properly. That way you will also have the benefit of Intellisense.
I am trying to write some VBA code in excel to automate my task of building 100+ .pdf word documents, each following the set template. I originally copied a code from a youtube tutorial showing how to build automated emails from a spreadsheet, and I felt my application was similar enough.
I can get the text replacements to occur as they should. My primary issue is getting images inserted where they need to be. I've attempted using a bookmark and replace code with no luck. I think my issue lies in my variables not having the correct value between the various subs, although that is only my uneducated best guess.
My next issue is creating a code to pull text from an existing document and paste into a new document. I'll be honest, I have been so stuck on the image issue that I haven't even looked into this yet.
I may be going about this task in an inefficient way, however, if someone may be able to spot the fault in my code, it would be greatly appreciated. I have pasted my existing code below. Hopefully it isn't too bad.
Option Explicit
Dim CustRow, CustCol, LastRow, TemplRow, j As Long
Dim DocLoc, TagName, TagValue, TemplName, FileName As String
Dim CurDt, LastAppDt As Date
Dim WordDoc, WordApp As Object
Dim WordContent As Word.Range
Sub CreateWordDocuments()
With Sheet1
If .Range("B3").Value = Empty Then
MsgBox "Please select a correct template from the drop down list"
.Range("G3").Select
Exit Sub
End If
TemplRow = .Range("B3").Value 'Set Template Row
TemplName = .Range("G3").Value 'Set Template Name
DocLoc = Sheet2.Range("F" & TemplRow).Value 'Word Document Filename
'Open Word Template
On Error Resume Next 'If Word is already running
Set WordApp = GetObject("Word.Application")
If Err.Number <> 0 Then
'Launch a new instance of Word
Err.Clear
'On Error GoTo Error_Handler
Set WordApp = CreateObject("Word.Application")
WordApp.Visible = True 'Make the application visible to the user
End If
LastRow = .Range("E9999").End(xlUp).Row 'Determine Last Row in Table
For CustRow = 8 To LastRow
Set WordDoc = WordApp.Documents.Open(FileName:=DocLoc, ReadOnly:=False) 'Open Template
For CustCol = 5 To 10 'Move Through 6 Columns
TagName = .Cells(7, CustCol).Value 'Tag Name
TagValue = .Cells(CustRow, CustCol).Value 'Tag Value
With WordDoc.Content.Find
.Text = TagName
.Replacement.Text = TagValue
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll 'Find & Replace all instances
End With
Next CustCol
Call InsertScreenshots
If .Range("I3").Value = "PDF" Then
FileName = ThisWorkbook.Path & "\" & .Range("E" & CustRow).Value & "_" & .Range("G" & CustRow).Value & ".pdf" 'Create full filename & Path with current workbook location, Category_Model
WordDoc.ExportAsFixedFormat OutputFileName:=FileName, ExportFormat:=wdExportFormatPDF
WordDoc.Close False
Else: 'If Word
FileName = ThisWorkbook.Path & "\" & .Range("E" & CustRow).Value & "_" & .Range("G" & CustRow).Value & ".docx"
WordDoc.SaveAs FileName
End If
.Range("O" & CustRow).Value = TemplName 'Template Name
.Range("P" & CustRow).Value = Now
Next CustRow
End With
End Sub
Sub FillABookmark(bookmarkname As String, imagepath As String)
Dim objWord As Object
Dim objDoc As Object
With Sheet1
On Error Resume Next
Set objWord = GetObject(, "Word.Application")
If objWord Is Nothing Then
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
objWord.Documents.Open "DocLoc"
End If
Set objDoc = objWord.ActiveDocument
With objDoc
.Bookmarks(bookmarkname).Select
.Shapes.AddPicture FileName:=imagepath
End With
End With
End Sub
Sub InsertScreenshots()
With Sheet1
For CustCol = 11 To 14 'Move Through 4 Columns
TagName = .Cells(7, CustCol).Value 'Tag Name
TagValue = .Cells(CustRow, CustCol).Value 'Tag Value
Call FillABookmark("TagName", "TagValue")
Next CustCol
End With
End Sub
There is a lot going on here and a lot of issues.
Key Points
Learn the value of proper indenting
Dim all variables, otherwise they will be Variants
Early Binding is easier to debug. Use explicit types rather than Object
Don't use Module scoped Variables unless you have a good reason
CodeNames can be useful, but give them meaningful names
Correct test for Empty is IsEmpty
GetObject ClassID is the 2nd parameter. I needed to use Word.Application.16, your installation may vary
Reset your error handling after using On Error Resume Next as soon as you can (this likely was hiding errors from you)
When using EndUp to find the last used row, search from the bottom of the sheet
Simplified the calling of your InsertScreenshots code
You already had a Word app and open doc, don't open it again
Simplified the Insert of image, avoid use of Select
Note: without a sample of your workbook and word doc I can't be sure there aren't other issues, you will need to continue the debug.
See inline comments on changes marked with ~~
Refactored code
Option Explicit
Sub CreateWordDocuments()
'~~ Don't use module scoped variables
'~~ declare all variable types, else they are Variants
Dim CustRow As Long, CustCol As Long, LastRow As Long, TemplRow As Long, j As Long
Dim DocLoc As String, TagName As String, TagValue As String, TemplName As String, FileName As String
Dim CurDt As Date, LastAppDt As Date
'~~ to make debugging easier, use Early Binding (add reference to Microsoft Word), to get Intellisence help. If you need late binding, change back later
Dim WordDoc As Word.Document, WordApp As Word.Application ' Object
Dim WordContent As Word.Range '~~ this suggests you are already using Early Binding!
With Sheet1 '~~ If you are going to use CodeNames, give the sheet a meaningful name (edit it in the Properties window)
If IsEmpty(.Range("B3").Value) Then '~~ correct test for Empty
MsgBox "Please select a correct template from the drop down list"
.Range("G3").Select '~~ will only work if Sheet1 is active
Exit Sub
End If
TemplRow = .Range("B3").Value 'Set Template Row
TemplName = .Range("G3").Value 'Set Template Name
DocLoc = Sheet2.Range("F" & TemplRow).Value 'Word Document Filename
'Open Word Template
On Error Resume Next 'If Word is already running
Set WordApp = GetObject(, "Word.Application.16") '~~ correct format for Office365 - YMMV
If Err.Number <> 0 Then
Err.Clear
On Error GoTo 0 '~~ reset error handling
'Launch a new instance of Word
Set WordApp = New Word.Application ' CreateObject("Word.Application")
WordApp.Visible = True 'Make the application visible to the user
End If
On Error GoTo 0 '~~ reset error handling
WordApp.Visible = True
LastRow = .Cells(.Rows.Count, 5).End(xlUp).Row '~~ use real last row 'Determine Last Row in Table
For CustRow = 8 To LastRow
Set WordDoc = WordApp.Documents.Open(FileName:=DocLoc, ReadOnly:=False) 'Open Template
For CustCol = 5 To 10 'Move Through 6 Columns
TagName = .Cells(7, CustCol).Value 'Tag Name
TagValue = .Cells(CustRow, CustCol).Value 'Tag Value
With WordDoc.Content.Find
.Text = TagName
.Replacement.Text = TagValue
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll 'Find & Replace all instances
End With
Next CustCol
For CustCol = 11 To 14 'Move Through 4 Columns ~~ do it here, it's cleaner and easier to reference the Row
TagName = .Cells(7, CustCol).Value '~~ Bookmark Name
TagValue = .Cells(CustRow, CustCol).Value '~~ Image path and name
FillABookmark TagName, TagValue, WordDoc '~~ call to insert each image
Next
If .Range("I3").Value = "PDF" Then
FileName = ThisWorkbook.Path & "\" & .Range("E" & CustRow).Value & "_" & .Range("G" & CustRow).Value & ".pdf" 'Create full filename & Path with current workbook location, Category_Model
WordDoc.ExportAsFixedFormat OutputFileName:=FileName, ExportFormat:=wdExportFormatPDF
WordDoc.Close False
Else '~~ don't need the :
FileName = ThisWorkbook.Path & "\" & .Range("E" & CustRow).Value & "_" & .Range("G" & CustRow).Value & ".docx"
WordDoc.SaveAs FileName
End If
.Range("O" & CustRow).Value = TemplName 'Template Name
.Range("P" & CustRow).Value = Now
Next CustRow
End With
End Sub
Sub FillABookmark(bookmarkname As String, imagepath As String, objDoc As Word.Document)
'~~ Use passed Parameter for Doc
'~~ Don't need select
objDoc.Bookmarks(bookmarkname).Range _
.InlineShapes.AddPicture FileName:=imagepath
End Sub
I created a macro in Excel where I can mail-merge data from Excel into Word Letter Template and save the individual files in the folder.
I have Employee data in Excel and I can generate any Employee letter using that Data and can save the individual Employee letter as per the Employee name.
I have run mail-merge automatically and save individual files as per the Employee name. And every time it runs the file for one person it will give the status as Letter Already Generate so that it wont duplicate any Employee records.
The problem is the output in all the merged files the output is same as the first row. Example: if my Excel has 5 Employee details I am able to save the 5 individual merged files on each employee name, however the merged data if of the first employee who is in Row 2.
My rows have the below data:
Row A: has S.No.
Row B: has Empl Name
Row C: has Processing Date
Row D: has Address
Row E: Firstname
Row F: Business Title
Row G: Shows the status (if the letter is generated it shows "Letter Generated Already" after running the macro or it shows blank if it is new record entered.
Also how can I save the output (merged file) also in PDF other than DOC file so the merged files will be in two formats one in DOC and the other one in PDF formats?
Sub MergeMe()
Dim bCreatedWordInstance As Boolean
Dim objWord As Word.Application
Dim objMMMD As Word.Document
Dim EmployeeName As String
Dim cDir As String
Dim r As Long
Dim ThisFileName As String
lastrow = Sheets("Data").Range("A" & Rows.Count).End(xlUp).Row
r = 2
For r = 2 To lastrow
If Cells(r, 7).Value = "Letter Generated Already" Then GoTo nextrow
EmployeeName = Sheets("Data").Cells(r, 2).Value
' Setup filenames
Const WTempName = "letter.docx" 'This is the 07/10 Word Templates name, Change as req'd
Dim NewFileName As String
NewFileName = "Offer Letter - " & EmployeeName & ".docx" 'This is the New 07/10 Word Documents File Name, Change as req'd"
' Setup directories
cDir = ActiveWorkbook.path + "\" 'Change if appropriate
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 `Data$`" ' Set this as required
With objMMMD.MailMerge 'With ActiveDocument.MailMerge
.Destination = wdSendToNewDocument
.SuppressBlankLines = True
With .DataSource
.FirstRecord = wdDefaultFirstRecord
.LastRecord = wdDefaultLastRecord
End With
.Execute Pause:=False
End With
End With
' Save new file
objWord.ActiveDocument.SaveAs cDir + NewFileName
' 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, 7).Value = "Letter Generated Already"
nextrow:
Next r
End Sub
To save the file in pdf format use
objWord.ActiveDocument.ExportAsFixedFormat cDir & NewFileName, _
ExportFormat:=wdExportFormatPDF
It looks to me that when you are executing the mail merge, it should create a file with ALL of the letters, so when you open it, it would appear that the first letter is the one that is getting saved, but if you scroll down the word file that you have saved, you may find each letter on a new page.
Instead, you want to execute the merge one letter at a time.
To fix this, change the lines as follows:
With .DataSource
.FirstRecord = r-1
.LastRecord = r-1
.ActiveRecord = r-1
You need to use r-1 because Word is going to use the record number in its dataset, and since the data starts in row 2, and the counter r is related to the row, you need r-1.
You don't need to open up word each time, so put all of the code setting the datasource of the mail merge and creating the word doc outside of your main loop.
Const WTempName = "letter.docx" 'This is the 07/10 Word Templates name,
Dim NewFileName As String
' Setup directories
cDir = ActiveWorkbook.path + "\" 'Change if appropriate
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 `Data$`" ' Set this as required
For r = 2 To lastrow
If Cells(r, 7).Value = "Letter Generated Already" Then GoTo nextrow
'rest of code goes here
Also, instead of checking the Excel file for the Employee name to create the file name, you could do this after you merge the document. For me, this is a little more intuitive to link the file name to the letter you have just merged. To do this update the line further to:
With .DataSource
.FirstRecord = r-1
.LastRecord = r-1
.ActiveRecord = r-1
EmployeeName = .EmployeeName 'Assuming this is the field name
Then immediately before saving the file you can do this:
' Save new file
NewFileName = "Offer Letter - " & EmployeeName & ".docx"
objWord.ActiveDocument.SaveAs cDir + NewFileName
Hope this helps.
The following code works as intended. It saves one .docx and one .pdf file for each entry in the data table while following OpiesDad's recommendations.
Before running, check if the VBA library for Word (Microsoft Word 16.0 Object Library) is activated and make the connection to the Excel data table from the Word template (Mail Merge settings).
Sub MergeMe()
Application.ScreenUpdating = False
Dim bCreatedWordInstance As Boolean
Dim objWord As Word.Application
Dim objMMMD As Word.Document
Dim EmployeeName As String
Dim cDir As String
Dim r As Long
Dim ThisFileName As String
lastrow = Sheets("Dados").Range("A" & Rows.Count).End(xlUp).Row
r = 2
' Setup filenames
Const WTempName = "Proposta.docx" 'Word Template name, Change as req'd
Dim NewFileName As String
On Error Resume Next
' Setup directories
cDir = ActiveWorkbook.Path + "\" 'Change if appropriate
ThisFileName = ThisWorkbook.Name
' 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"
Exit Sub
End If
' 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)
'loop through each table row
For r = 2 To lastrow
If Cells(r, 7).Value = "Letter Generated Already" Then GoTo nextrow
objMMMD.Activate
'Merge the data
With objMMMD
.MailMerge.OpenDataSource Name:=cDir + ThisFileName, sqlstatement:="SELECT * FROM `Dados$`" ' 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 = .DataFields("Nome").Value 'Change "Nome". to the column name for employee names"
End With
.Execute Pause:=False 'executes the mail merge
End With
End With
On Error GoTo 0
' Save new file (.docx & .pdf) and close it
NewFileName = "Offer Letter - " & EmployeeName 'Word Document File Name, Change as req'd"
objWord.ActiveDocument.SaveAs cDir + NewFileName + ".docx"
objWord.ActiveDocument.ExportAsFixedFormat cDir + NewFileName + ".pdf", _
ExportFormat:=wdExportFormatPDF
objWord.ActiveDocument.Close
Cells(r, 7).Value = "Letter Generated Already"
nextrow:
Next r
objMMMD.Close False
objWord.Quit
Application.ScreenUpdating = True
End Sub
This question already has an answer here:
Ignore Excel Files That Are Password Protected [duplicate]
(1 answer)
Closed 8 years ago.
I have a project in which I have to go over 1,000+ excel files in a folder, and see which ones are password protected and which ones aren't. In order to save time, I wrote a macro to do this, which is as follows:
Sub CheckWbook()
Dim Value As String, a As Single, myfolder as string
With Application.FileDialog(msoFileDialogFolderPicker)
.Show
myfolder = .SelectedItems(1) & "\"
End With
Range("C4") = myfolder
Range("B7:C" & Rows.Count) = ""
a = 0
Value = Dir(myfolder)
Do Until Value = ""
If Value = "." Or Value = ".." Then
Else
If Right(Value, 3) = "xls" Or Right(Value, 4) = "xlsx" Or Right(Value, 4) = "xlsm" Then
On Error Resume Next
Workbooks.Open Filename:=myfolder & Value, Password:="zzzzzzzzzzzz"
If Err.Number > 0 Then
Range("C7").Offset(a, 0).Value = "Yes"
End If
Workbooks(Value).Close False
On Error GoTo 0
Range("B7").Offset(a, 0).Value = Value
a = a + 1
End If
End If
Value = Dir
Loop
End Sub
The problem I'm having is that the popup for the password is still present: it does not fill in the password. Any help would be highly appreciated. -A
Edit
Changed the code a bit, and got past the error message, but now I'm getting stuck at the password popup, that stops the macro from completely working, despite the On Error Resume Next feature.
Then, I came across this code that I thought could help:
Option Explicit
Public Sub ProcessBatch()
Dim strFileName As String
Dim strFilePath As String
Dim oDoc As Document
' Set Directory for Batch Process
strFilePath = "C:\Test\"
' Get Name of First .doc File from Directory
strFileName = Dir$(strFilePath & "*.doc")
While Len(strFileName) <> 0
' Set Error Handler
On Error Resume Next
' Attempt to Open the Document
Set oDoc = Documents.Open( _
FileName:=strFilePath & strFileName, _
PasswordDocument:="?#nonsense#$")
Select Case Err.Number
Case 0
' Document was Successfully Opened
Debug.Print strFileName & " was processed."
Case 5408
' Document is Password-protected and was NOT Opened
Debug.Print strFileName & " is password-protected " & _
"and was NOT processed."
' Clear Error Object and Disable Error Handler
Err.Clear
On Error GoTo 0
' Get Next Document
GoTo GetNextDoc
Case Else
' Another Error Occurred
MsgBox Err.Number & ":" & Err.Description
End Select
' Disable Error Handler
On Error GoTo 0
'-------------------------------------
'-------------------------------------
'---Perform Action on Document Here---
'-------------------------------------
'-------------------------------------
' Close Document
oDoc.Close
' Clear Object Variable
Set oDoc = Nothing
GetNextDoc:
' Get Next Document from Specified Directory
strFileName = Dir$()
Wend
End Sub
but this fails to recognize the oDoc as a Document. Any ideas on how to get it working?
to open the excel file? or sheet
if it is a sheet should be
ActiveSheet.Unprotect Password: = "yourpassword"
if it is an excel
ActiveWorkbook.Unprotect("youtpassword")
I hope it serves you a hug I learned a lot here I hope you will also hopefully serve my help