Edit Outlook locally saved .msg body by replacing text in VBA - excel

Good afternoon,
I have an Outlook .msg email saved at a local folder in my computer.
Is there any way I can replace the word "AAAA" in the body with any word I want in VBA?
Is there any way I can change the To: field?
The goal is to run an Excel table and create copies of a template message, replace the To: field and some words of the template with the info in the Excel table and save it. We will manually send latter.
I only need the .msg file modifying code (To: field and body replaces). The loop is already coded.
Thank you so much,

The Outlook object model doesn't provide anything to edit MSG files out of the box. But you can automate Outlook to create an item, edit it and then save it back as a template.
Use the Application.CreateItemFromTemplate method which creates a new Microsoft Outlook item from an Outlook template (.oft) and returns the new item. So, you can create a new item based on the template saved on the disk and then replace everything you need there. Then you could save it back as a template or send the item out. Read more about that in the How To: Create a new Outlook message based on a template article.

You can use Application.Session.OpenSharedItem to open an MSG file, modify the returned MailItem object (Subject / HTMLBody / Recipients), then call MAilItem.Save to update the MSG file.

If anyone needs, here it is the code I used. Do not focus on the for loops, but in the way the msg is loaded, edited and saved.
In this example some words in the msg file are replaced for the values in an excel table, as well as the TO: (email receiver). e.g. word AA in a msg file is changed with the value of the C7 cell.
The aim is to create a msg as a template with some key words (AA, BB, CC, etc), copy that template, replace those words with the ones in the excel table and save the new msg file.
Sub Recorrer()
Dim x As Integer
Dim fsObject As Object
Dim outApp As Object 'Outlook.Application
Dim outEmail As Object 'Outlook.MailItem
Dim outRecipient As Object 'Outlook.Recipient
On Error Resume Next
Set outApp = GetObject(, "Outlook.Application")
If outApp Is Nothing Then
MsgBox "Outlook is not open"
Exit Sub
End If
On Error GoTo 0
Set fsObject = CreateObject("Scripting.FileSystemObject")
' Set numcols = number of cols to be replaced.
NumCols = Range("C1", Range("C1").End(xlToRight)).Cells.Count
' Set numrows = number of rows of data.
NumRows = Range("A2", Range("A2").End(xlDown)).Rows.Count
' Select cell a1.
Range("A2").Select
' Establish "For" loop to loop "numrows" number of times.
For x = 1 To NumRows
fsObject.CopyFile ThisWorkbook.Path & "\" & Range("B" & x + 1) & ".msg", ThisWorkbook.Path & "\Correos\" & Range("B" & x + 1) & "_" & Range("C" & x + 1) & ".msg"
Set outEmail = outApp.Session.OpenSharedItem(ThisWorkbook.Path & "\Correos\" & Range("B" & x + 1) & "_" & Range("C" & x + 1) & ".msg")
outEmail.Recipients.Add Range("A" & x + 1)
For Z = 1 To NumCols
'MsgBox Cells(x + 1, Z + 2)
outEmail.HTMLBody = Replace(outEmail.HTMLBody, Cells(1, Z + 2), Cells(x + 1, Z + 2))
Next
outEmail.Save
' Selects cell down 1 row from active cell.
ActiveCell.Offset(1, 0).Select
Next
End Sub

Related

How to save a Word template file as a .docx in VBA? Or create multiple specific documents from one excel sheet?

I am trying to copy a bunch of text data from an excel spreadsheet into multiple separate word documents (one excel row = one document, but each column's heading has to be included before the text from the respective field of the row). I also want these documents' names to be the text from specific fields in the spreadsheet (row headers).
Because I want fancy formatting, and a specific order of copying/pasting things (not all fields are included), I am using a word template with bookmarks that I can feed into VBA. It fills the template well, but I cannot save it as a standard word document before repeating the loop. I get the error 'Object doesn't support this property or method'. Is there a way to overcome it, or a more elegant method I've missed?
Here is the code:
Sub Primitive()
Dim objWord As Object
Dim ws As Worksheet
Dim i As Integer
Set ws = ThisWorkbook.Sheets("Sheet1")
i = 2 ' First row to process
'Start of loop
Do Until ws.Range("B" & i) = ""
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
'Change to local path of template file
objWord.Documents.Open "C:path of template file.dotx"
With objWord.ActiveDocument
.Bookmarks("FirstBookmark").Range.Text = ws.Range("B" & i).Value & " " & ws.Range("C" & i)
.Bookmarks("headlineZ").Range.Text = ws.Range("Z1").Value
lots of this^ here to arrange the data right in the document
NewFileName = "C:\path of where I want the new file" & ws.Range("C" & i).Value & ".docx"
This is the line that gives the error 483:
objWord.SaveAs2 Filename:="NewFileName"
End With
objWord.Close
Set objWord = Nothing
i = i + 1
Loop
End Sub
You are trying to save Word, rather than the document. If you replace this
objWord.SaveAs2 Filename:="NewFileName"
with this
objWord.ActiveDocument.SaveAs2 Filename:="NewFileName"
it should work better. That said, you should not use ActiveDocument in your macros. Consider replacing
objWord.Documents.Open "C:path of template file.dotx"
with something like
Set TargetDocument = objWord.Documents.Open("C:path of template file.dotx")
and replace all ActiveDocument with TargetDocument.

Pulling file names from SharePoint and saving to SharePoint, using VBA

I'm trying to adapt an Excel form I created that uses drive locations to save copies of the form, to work with SharePoint in a similar manner. Currently, the first macro is set up such that it will search the contents of a particular folder to determine the next available number in the queue (i.e. if 1, 2 and 4 already exist, it will assign 3) and save the sheet as that next available number. When the sheet is complete, the second macro will save the file with a specified name based on data within the sheet, in another specified location (again based on data defined within the sheet). The drive is in the process of being retired in our company and everything moved to Cloud-based storage, so I would like a way to complete the same actions but using SharePoint directories.
The code for the first macro is as follows:
Dim strDir As String
Dim file As Variant
Dim savename As Integer
Dim savename_string As String
strDir = "R:\Queue\"
savename = 1
savename_string = CStr(savename)
file = Dir(strDir)
While (CInt(savename_string) = savename)
If file <> (savename & ".xlsm") Then
If file = "" Then
savename = savename + 1
Else
file = Dir
End If
ElseIf file = (savename & ".xlsm") Then
savename = savename + 1
savename_string = CStr(savename)
file = Dir(strDir)
End If
Wend
ActiveWorkbook.SaveAs ("R:\Queue\" & savename_string & ".xlsm")
And then the code for the second macro is as follows:
Dim answer As Integer
Dim error As Integer
Dim delete As String
answer = MsgBox("Are you sure you want to save sheet & close?", vbYesNo + vbQuestion, "WARNING")
If answer = vbYes Then
'Define PWO, assembly, terminal, strand, and gauge As Strings, and define which cells they are on the sheet
delete = ActiveWorkbook.Name
ActiveWorkbook.SaveAs ("R:\" & terminal & assembly & Space(1) & gauge & strand & Space(1) & PWO & Space(1) & Format(Now(), "MM-DD-YYYY") & ".xlsm")
Kill ("R:\Queue\" & delete)
ActiveWorkbook.Close
Else
Exit Sub
End If
Currently the second macro works correctly when replacing the locations with the SharePoint URL locations, but when doing the same with the first macro, it returns an error message "Bad file name or number" at the line file = Dir(strDir). Can I get this code in working order, or is there a better way I should go about this? Thanks!

Editing an embedded word template and saving it without any changes being made to template

i wrote the following code in VBA. I'm able to save the template onto the disk but the changes made are also made on the template which is then saved. I want to save the template with the information separately onto the disk and close the template without any changes being made to it. Also after I insert the details into header / footer, i used code to close the header / footer pane. That no longer works and now shows an extra page since i have separate header / footer for each page. How can i do this with the embedded word template since this worked if i kept the word template outside
Private Sub M114_Click()
Dim oleObject As Object
Dim wDoc As Object
Set oleObject = ActiveWorkbook.Sheets("Properties").OLEObjects(1)
oleObject.Verb Verb:=xlPrimary
ActiveSheet.Range("A1").Select
Set wDoc = oleObject.Object
' Creates the last row that will be used
'lRow = ThisWorkbook.Sheets("Input").Cells(Rows.Count, 1).End(xlUp).Row
' Loop through all the rows
'For i = 3 To lRow
i = 3
' Control 1/21 - Date of Letter
wDoc.ContentControls(1).Range.Text = ThisWorkbook.Sheets("Input").Cells(i, 2)
wDoc.ContentControls(21).Range.Text = ThisWorkbook.Sheets("Input").Cells(i, 2)
' Control 2/14 - Bank Contact Name
wDoc.ContentControls(2).Range.Text = ThisWorkbook.Sheets("Input").Cells(i, 13)
wDoc.ContentControls(14).Range.Text = ThisWorkbook.Sheets("Input").Cells(i, 13)
' Update Headers from page 3 to page 5
For j = 3 To 5
With wDoc.Sections(j).Headers(wdHeaderFooterPrimary).Range
.InsertAfter Text:=vbCrLf & UCase(ThisWorkbook.Sheets("Input").Cells(i, 6))
.InsertAfter vbTab
.InsertAfter Text:=vbCrLf & vbCrLf & UCase(ThisWorkbook.Sheets("Input").Cells(i, 7))
.InsertAfter vbTab
.InsertAfter Text:=vbCrLf & vbCrLf & ("At close of business on 31 December " & DatePart("yyyy", ThisWorkbook.Sheets("Input").Cells(i, 4)))
End With
Next j
'''' Issue with this resolve this
' Close the header / footer pane
wDoc.ActiveWindow.ActivePane.View.SeekView = wdSeekCurrentPageHeader
wDoc.ActiveWindow.ActivePane.View.SeekView = wdSeekMainDocument
' Create the file name and save and close the file
file_name = Application.WorksheetFunction.Trim("BankConf-" & ThisWorkbook.Sheets("Input").Cells(i, 6) & "-" & ThisWorkbook.Sheets("Input").Cells(i, 7) & ".doc")
wDoc.SaveAs2 (ThisWorkbook.Path & "/" & file_name)
'wDoc.ActiveDocument.Close SaveChanges:=wdDoNotSaveChanges
wDoc.Application.Quit
Side comment first: usually one would do what you want with mail merge in word ...
Regarding your question:
First of all - you should add a document (docx) as oleobject not a template (dotx). The template shows a somewhat strange behaviour.
Second it is necessary that you first do the saveAs, then open the new file to a separate doc-variable. By that the original oleObject-doc will not be edited.
Furthermore I would suggest two enhancements that make your code much more readable and more robust:
Insert a table for your data (= listobject in VBA) - then you can address the column-names in the code which is easier to maintain and to read than .cells(i,6).
You then can use these column names as tags for the content controls (ccs) in the word document. It is possible to name two different ccs with the same tag name. There is a method selectContentControlsByTag that returns all ccs with the same tag-name. Even those from headers and footers. So you should according ccs in the headers as well.
(Referencing a cc by index is critical as the index may change if you add a new cc or move them around or add text or ...)
As I understand you only insert some of the values to the letter. Therefore I suggest to postfix these column names e.g. by _cc.
This is the modified code - I added Microsoft Word as a reference to the VBA project.
Option Explicit
Sub createAll()
Dim docSource As Word.Document
Set docSource = getSourceDoc 'from oleObject
'assumption your data are in a table --> insert > table
'column names that have values that should go into the letter are named [CC-Tag]_CC
'example: columns name = DateOfLetter_CC | content controls tag= DateOfLetter
Dim lo As ListObject
Set lo = ThisWorkbook.Sheets("Input").ListObjects(1)
Dim lr As ListRow, lc As ListColumn
Dim docTarget As Word.Document, cc As ContentControl
'loop all rows of data table
For Each lr In lo.ListRows
'get empty word doc for this entry
Set docTarget = getTargetDoc(docSource, getFullFilename(lr))
For Each lc In lo.ListColumns
'within the word doc each CC has an according tag (without postfix)
If Right(lc.Name, 3) = "_CC" Then
For Each cc In docTarget.SelectContentControlsByTag(Split(lc.Name, "_")(0))
'there can be multiple CCs with the same tag
'tags within headers/footers are also handled within this loop
cc.Range.Text = lr.Range.Cells(1, lc.Index)
Next
End If
Next
docTarget.Close True
Next
'close Word
docSource.Application.Quit
MsgBox "ready"
End Sub
Private Function getFullFilename(lr As ListRow) As String
'you have to adjust this to your needs, i.e. add the correct column names to build the filename
Dim lo As ListObject
Set lo = lr.Parent
With lr.Range
getFullFilename = ThisWorkbook.Path & "\" & .Cells(1, lo.ListColumns("BankContactName_CC").Index).Value & ".docx"
End With
End Function
Private Function getSourceDoc() As Word.Document
'retrieves the oleDoc which is later used to save copies from
Dim oleObject As oleObject
Set oleObject = ThisWorkbook.Sheets("Properties").OLEObjects(1)
oleObject.Verb xlVerbOpen
Set getSourceDoc = oleObject.Object
End Function
Private Function getTargetDoc(docSource As Word.Document, FullFilename As String) As Word.Document
'saveas new file - open new file
'this is then returned
docSource.SaveAs2 FullFilename
Dim wrdApp As Word.Application
Set wrdApp = docSource.Application
Set getTargetDoc = wrdApp.Documents.Open(FullFilename)
End Function

Find command - How to match names on multiple tabs and input data?

I am working on a spreadsheet that has to be completed quarterly so am looking to automate a lot of the process. I have a master tab - "#" and 16 Team tabs. On the # is a table which includes all 16 team names, I would like this table to update depending on what Teams have signed off.
Quick runthrough:
Spreadsheet is emailed out to multiple teams, each log in and review Products. After all products have been reviewed, they press the "Sign Off" button. This button does 3 things;
Inputs username and date to right of button
Sends email to spreadsheet owner
Updates table on "#" tab.
Number 3 is where I am having the issue. I have tried find, if, functions - lots of different options but just can't get it to work. Functions didn't work as the spreadsheet is reset every quarter so the cell values are cleared, so it needs to be VBA.
Some previous options I tried:
Sub If_Team 1()
'Set variables
Set sht1 = Sheets("#")
Set sht2 = Sheets("Team 1")
'Team1
If sht2.Range("M2:N2") <> "" Then
sht1.Range("C4:D4") = sht2.Range("M2:N2")
sht1.Range("B4") = "P"
Else
sht1.Range("C4:D4") = ""
sht1.Range("B4") = "O"
End If
Unfortunately this worked until I put in more If functions, where it then pasted the data in the whole table rather than just Team 1. The below also worked, until again adding more values where it pasted the data in every field where the criteria was met (which was them all).
If pfID = "Team 1" Then GoTo 1 Else
If pfID = "Team 2" Then GoTo 2 Else
1 sht2.Cells(3, 2).Value = "P"
sht2.Cells(3, 3).Value = Date
sht2.Cells(3, 4).Value = Environ("username")
On each team tab is the team name, so lets say "Team 1". Team 1 is found in Cell "F1" on the Active Team Sheet. On the # tab in the table, Team 1 is Cell "A3".
What I would like to happen is ActiveSheet.Range("F1") to find the same name on the # (sht2) tab, and then do the following if the names match (so as we know Team 1 on the # tab is "A3"):
sht2.Range("A4").Value = "P"
sht2.Cells("A5").Value = Date
sht2.Cells("A6").Value = Environ("username")
This way the spreadsheet owner will only need to review the # tab to see who has signed off, rather than go through each tab. However I don't want it to point to an invdividual cell like above as I would like it to find and match the names.
Here is the full code so far:
Sub Button2_Click() 'SIGN OFF BUTTON
Dim cellAddr As String
Dim aCol As Long
' Declare variables
Dim c As Integer ' Column
Dim emBody As String ' Body text of email
Dim emCnt As Integer ' Count of email addressees
Dim emTitl As String ' Subject line of email
Dim emTxt As String ' List of email addressees
Dim myOutlook As Object ' MS Outlook application
Dim mySendmail As Object ' The email to be sent
Dim pfID As String ' Platform ID
Dim r As Integer ' Row
'Set variables
Set sht1 = ActiveSheet
Set sht2 = Sheets("#")
'Cell Address
cellAddr = ActiveSheet.Shapes(Application.Caller).TopLeftCell.Address
'Column Number
aCol = ActiveSheet.Shapes(Application.Caller).TopLeftCell.Column
'Input Date and Username
If aCol <> 1 Then _
sht1.Range(cellAddr).Offset(, 2).Value = Date
sht1.Range(cellAddr).Offset(, 1).Value = Environ("username")
' Obtain Platform details
pfID = ActiveSheet.Range("F1").Value
'Version ID
vID = sht2.Range("D1").Value
**'Input Sign Off on "#" Tab**
' Email subject line
emTitl = pfID & " - Out of Support Software Review " & vID & " Completed"
' Email body text
emBody = "<BODY style=font-size:12pt;font-family:Calibri>" & "Hi," & "<br>" & "<br>" & "Out of Support Software Review " & "<b>" & vID & "</b>" & " Completed for " & "<b>" & pfID & "</b>" & "."
Set myOutlook = CreateObject("Outlook.Application")
Set mySendmail = myOutlook.CreateItem(olMailItem)
With mySendmail
.to = ""
.Subject = emTitl
.HTMLBody = emBody
.Display
End With
' CLEAR.
Set objEmail = Nothing: Set objOutlook = Nothing
ErrHandler:
'
End Sub
Any help is appreciated, any questions let me know! Sorry if this is slightly confusing.

Loop to copy text from two columns to Word

I need to copy name and last name from Excel, paste it into a Word template and print. I have an Excel file from which I need to copy two columns from each row (i.e. E31:F31,E40:F40) into a bookmark in Word and then print it.
I need to loop either from row X to Y or X number of times. The Excel file is not well formatted. I managed to apply paste&print to a custom number of cells, but I get an error probably because I try to do it all at once.
document is in use do you want to open a temp copy
Copied text shows up in Word with big gaps between two words (what came from columns E and F). How do I fix?
Sub Copy_Excel_Cell_to_Word_Form()
Dim wdApp As Object 'Word.Application
Dim wdDoc As Object 'Word.Document
For i = 31 To 60
'Open new instance of Microsoft Word
Set wdApp = CreateObject("Word.Application")
'Make application visible
'wdApp.Visible = False
'Open the word document
Set wdDoc = wdApp.Documents.Open("\\example.doc")
'Copy value
Worksheets(1).Range("E" & i, "F" & i).Copy
'Paste to word document
wdDoc.Bookmarks("WORKER").Range.PasteAndFormat (wdFormatPlainText)
wdDoc.PrintOut
Next
End Sub
For your second point, try this:
Declare a string variable called workerName. Instead of Worksheets(1).Range("E" & i, "F" & i).Copy use
workerName = Worksheets(1).Cells(i,5).Value 'e = 5
workerName = workerName & " " & Worksheets(1).Cells(i,6).Value & vbCr 'f = 6
And instead of use wdDoc.Bookmarks("WORKER").Range.PasteAndFormat (wdFormatPlainText) use
wdDoc.Bookmarks("WORKER").Range.Text = workerName
This should clear up the big gaps.

Resources