I'm trying to write a program that updates a Word document's metadata with content control boxes. The Excel document then loops through a directory that holds these documents and catalogs them by the metadata. In Word, I can set built-in properties in the DocumentBeforeSave sub via...
Application.ActiveDocument.BuiltInDocumentProperties("Keywords").Value = _
Application.ActiveDocument.SelectContentControlsByTitle("Tags").Item(1).Range.Text
and retrieve those in Excel via the shell...
Set objShell = CreateObject("Shell.Application")
Set objFolder = objShell.Namespace(WB.Path & "\QA\")
For Each strFileName In objFolder.Items
inputStr = objFolder.GetDetailsOf(strFileName, 18) 'Tags
'...
'...
next strFileName
The problem is that there are indexes in the "GetDetailsOf" function that I want to use but can't figure out how to set in Word. For example, I'd like to set the Division (GetDetailsOf index 155), Group (172), Program (277), and Attachments (219) fields. How do you set properties such as these that are not built-in?
Related
I would like to automatically print emails to PDF from outlook.
I haven't found a way to automate the print dialogue. There are a couple other threads dealing with this same issue in Outlook VBA, but no clear solution (I thought it would be simple!)
For example, I have a rule in outlook that automatically moves receipts to a specific folder. I'd like to automatically print these to PDF. I've tried to accomplish this by...
For Loop: Go through each unread item in the specified folder
Print: MailItem.Printout Method
Print Dialogue: Input path and filename and click OK. I haven't found any means of automating this process
Sub PrintReceipts()
'==============================================
'Declare variables, set namespace, define outlook folder (example names used below)
'==============================================
Dim olApp As Outlook.Application
Dim objNS As Outlook.NameSpace
Dim olFolder As Outlook.MAPIFolder
Dim msg As Outlook.MailItem
Dim Path As String
Dim Name As String
Set olApp = Outlook.Application
Set objNS = olApp.GetNamespace("MAPI")
Set olFldr = objNS.GetDefaultFolder(olFolderInbox).Folders("subfolder 1").Folders("subfolder 2")
'==============================================
'For each unread message save to Path with Name and mark as Read (path is just an example)
'==============================================
For Each msg In olFldr.Items
If msg.UnRead Then
Path = "C:\Users\User\Desktop\"
Name = msg.Subject & ".pdf"
msg.PrintOut
'=================================================
'Here is where I get lost.
'Print Dialogue opens. I have tried SendKeys but it does not work
'=================================================
msg.UnRead = False
End If
Next
End Sub
Alternative: I am wondering if I can do the following...
Save for Word: MailItem.SaveAs, to save the item as an .MHT
Open Word: Somehow open Word and apply ActiveDocument.ExportAsFixedFormat to export as PDF
Close Word and go back to Outlook
I hope someone may have an idea!
First of all, iterating over all items in the folder is not really a good idea in Outlook. Instead, you need to use the Find/FindNext or Restrict methods of the Items class. These methods allow getting items that correspond to your search criteria only. Read more about these methods in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
To save the message body using the PDF file format there is no need to use the SaveAs method of the MailItem class. The WordEditor property of the Inspector class returns an instance of the Word Document class which represents the message body. You can call the ExportAsFixedFormat method of the Document class directly from Outlook avoiding any disk operations.
Dim objDoc As Object, objInspector As Object
Set objInspector = myItem.GetInspector
Set objDoc = objInspector.WordEditor
objDoc.ExportAsFixedFormat folderPath & fileName & ".pdf", 17
Set objInspector = Nothing
Set objDoc = Nothing
See Chapter 17: Working with Item Bodies for more information.
I tried to extract the data from an Outlook email using VBA:
Sub DLPExtract()
Dim OutlookApp As Outlook.Application
Dim OutlookNamespace As Namespace
Dim Folder As MAPIFolder
Dim OutlookMail As MailItem
Dim i As Integer
Set OutlookApp = New Outlook.Application
Set OutlookNamespace = OutlookApp.GetNamespace("MAPI")
Set Folder = OutlookNamespace.GetDefaultFolder(olFolderInbox).Folders("gfdo#aviva.com\Inbox")
i = 1
For Each OutlookMail In Folder.Items
If InStr(OutlookMail.Subject, "Data Loss Prevention Report: GFDO DLP Daily Report Retrospective") > 0 And OutlookMail.ReceivedTime >= Range("From_date").Value Then
Range("Date").Offset(i, 0).Value = OutlookMail.Date
Range("Type").Offset(i, 0).Value = OutlookMail.
Range("Reference").Offset(i, 0).Value = OutlookMail.ID
Range("eMail_text").Offset(i, 0).Value = OutlookMail.Body
i = i + 1
End If
Next OutlookMail
Set Folder = Nothing
Set OutlookNamespace = Nothing
Set OutlookApp = Nothing
End Sub
I want to extract only IDs in the fourth column but first need to check if that is already in the respective column on Excel sheet. If yes, do nothing and If no, then take that ID out from this fourth column in email and paste it in respective column on Excel sheet.
I believe you are trying to check the email body format.
If that is the case then follow this link which talks about the "Bodyformat" property.
Range("Type").Offset(i, 0).Value = OutlookMail.BodyFormat
It is not clear what "Type" property you are interested in - the Outlook object model doesn't provide the Type property for their items. Instead, you may be interested in the MessageClass property which returns or sets a string representing the message class for the Outlook item. The MessageClass property links the item to the form on which it is based. When an item is selected, Outlook uses the message class to locate the form and expose its properties, such as Reply commands.
Also you may be interested in the BodyFormat property which returns or sets an OlBodyFormat constant indicating the format of the body text. The body text format determines the standard used to display the text of the message. Microsoft Outlook provides three body text format options: Plain Text, Rich Text (RTF), and HTML.
Finally, there is no need to iterate over all items in the folder and checking whether they correspond to the predefined condition:
For Each OutlookMail In Folder.Items
If InStr(OutlookMail.Subject, "Data Loss Prevention Report: GFDO DLP Daily Report Retrospective") > 0 And OutlookMail.ReceivedTime >= Range("From_date").Value Then
Instead, I'd recommend using the Find/FindNext or Restrict methods of the Items class. Read more about them in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
I'm getting this error, "Microsoft Excel is waiting for another application to complete an OLE action" when trying to automate a PDF string search and record findings in excel. For certain PDFs this error is not popping. I assume this is due to the less optimized PDFs taking a longer time to search string while indexing page by page.
To be more precise, I have a workbook containing two sheets. One contains a list of PDF file names and the other has a list of words that I want to search. From the file list the macro would open each PDF file and take each word from the list of words and perform a string search. If found it would record each finding in a new sheet in the same workbook with the file name and the found string.
Below is the code I'm struggling with. Any help is welcome.
Public Sub SearchWords()
'variables
Dim ps As Range
Dim fs As Range
Dim PList As Range
Dim FList As Range
Dim PLRow As Long
Dim FLRow As Long
Dim Tracker As Worksheet
Dim gapp As Object
Dim gAvDoc As Object
Dim gPDFPath As String
Dim sText As String 'String to search for
FLRow = ActiveWorkbook.Sheets("List Files").Range("B1").End(xlDown).Row
PLRow = ActiveWorkbook.Sheets("Prohibited Words").Range("A1").End(xlDown).Row
Set PList = ActiveWorkbook.Sheets("Prohibited Words").Range("A2:A" & PLRow)
Set FList = ActiveWorkbook.Sheets("List Files").Range("B2:B" & FLRow)
Set Tracker = ActiveWorkbook.Sheets("Tracker")
'For each PDF file list in Excel Range
For Each fs In FList
'Initialize Acrobat by creating App object
Set gapp = CreateObject("AcroExch.App")
'Set AVDoc object
Set gAvDoc = CreateObject("AcroExch.AVDoc")
'Set PDF file path to open in PDF
gPDFPath = fs.Cells.Value
' open the PDF
If gAvDoc.Open(gPDFPath, "") = True Then
'Bring the PDF to front
gAvDoc.BringToFront
'For each word list in the range
For Each ps In PList
'Assign String to search
sText = ps.Cells.Value
'This is where the error is appearing
If gAvDoc.FindText(sText, False, True, False) = True Then
'Record findings
Tracker.Range("A1").End(xlDown).Offset(1, 0) = fs.Cells.Offset(0, -1).Value
Tracker.Range("B1").End(xlDown).Offset(1, 0) = ps.Cells.Value
End If
Next
End If
'Message to display once the search is over for a particular PDF
MsgBox (fs.Cells.Offset(0, -1).Value & " assignment complete")
Next
gAvDoc.Close True
gapp.Exit
set gAVDoc = Nothing
set gapp = Nothing
End Sub
I have now found the answer to this problem.
I'm using Acrobat Pro and whenever I open a PDF file, it opens with limited features due to Protected View settings. If I disable this function or if I click Enable All Features and save changes to the PDF files, VBA macro runs smooth.
It's funny, I'm posting an answer to my own problem.
I need to save formatted text from Word in an Access Database.
So far I've managed to figure out how to store formatted text in an Access Field (Create a Memo Field in a Table and set the Text Format as Rich Text). Searching SO I have not yet come across a solution as to how to transport said text from word into Access.
I know that it is possible, because you can do it by simply copying and pasting the information if you are doing it manually.
My question, how can I copy formatted text from word into a field in a table using VBA?
Experimentally I created the following to test this. So far without success...
Sub GetComments()
'Imports Analyst Comments from Excel files als OLE Objects.
'---------------------------------
'Access Variables
Dim dbsFundDB As DAO.Database
Dim rsComments As DAO.Recordset
Set dbsFundDB = CurrentDb
Set rsComments = dbsFundDB.OpenRecordset("tblFunds")
'Word Variables
Dim doc As Word.Application
Dim dcmt As Word.Document
Dim sectn As Word.Section
Dim obCommentText As Object
Dim sAnalystText As String
'Open New Word File
Set doc = New Word.Application
doc.Visible = True
Set dcmt = doc.Documents.Open(sPathTemplate)
Set sectn = dcmt.Sections(1)
sectn.Range.Select
Selection.InsertFile FileName:="myfile.rtf", Range:="", _
ConfirmConversions:=False, Link:=False, Attachment:=False
sAnalystText = sectn.Range.Tables(1).cell(1, 1).Range.FormattedText
rsComments.AddNew
rsComments![Long Comment Exec] = sAnalystText
rsComments.Update
sectn.Range.Select
dcmt.Close savechanges:=False
doc.Quit
End Sub
UPDATE
I tried implementing the answer from Matt Hall. While the text is indeed copied to the database, it does not yet keep the formatting:
Here is my implementation as a simple test:
Option Explicit
Public Const sPathTemplate As String = "W:\L\BDTP\Products\FundResearchTool\Advisory.docx"
Option Compare Database
Sub GetComments()
'Imports Comments from word and save in DB
'Test soubroutine
'---------------------------------
'Word Variables
Dim obCommentText As Variant
Dim strSQL As String
obCommentText = GetWordContent(sPathTemplate)
strSQL = "insert into [tblText]([TestField]) values('" & obCommentText & "')"
DoCmd.RunSQL strSQL
MsgBox "Import Successful", vbInformation Or vbOKOnly
End Sub
Private Function GetWordContent(strFile As String) As Variant
' This function takes the path obtained to the MS-Word Document selected in
' the FileToOpen function and then uses that to open that MS-Word Document
' and retrieve its text contents
Dim objDoc As Word.Document
Set objDoc = GetObject(strFile)
GetWordContent = CVar(objDoc.Sections(1).Range.Text)
objDoc.Close
End Function
Here's a method that heavily references this.
Before you start make sure you have these (or your Access version's equivalent) references ticked in VBA editor > Tools > References:
Microsoft Word 15.0 Object Library
Microsoft Office 15.0 Object Library
Assuming you've set up a form with a command button to trigger this MS-Word import, put the following function and subroutine somewhere in that form's VBA module:
1) File Picker Function:
This will allow you to select the MS-Word Document you want to using the old familiar file dialogue window you see throughout Windows. Ultimately, all it does is save the file path and name of the file you've picked for use in in the subroutine described in (2)...
Private Function FileToOpen() As String
' This function will essentially allow you to browse to MS-Word document
' and then store the path of that file for use in the GetWordContent function
Dim fDialog As Office.FileDialog
Dim varFile As Variant
Set fDialog = Application.FileDialog(msoFileDialogFilePicker)
With fDialog
.AllowMultiSelect = False
.Title = "Select Word document to import"
.Filters.Clear
.Filters.Add "Word files", "*.doc?"
If _
.Show = True _
Then
For Each varFile In .SelectedItems
FileToOpen = varFile
Next
Else
FileToOpen = ""
End If
End With
End Function
2) Get Formatted Text Contents of MS-Word Document Subroutine:
This subroutine will use the file path and name of the MS-Word Document selected in the File Picker function (above) to open the MS-Word document, select all the text, copy it to the clipboard, paste it to a text box on an open form in Access and then close MS-Word...
Private Sub GetWordContent(strFile As String)
' This function takes the path obtained to the MS-Word Document selected in
' the FileToOpen function and then uses that to open that MS-Word Document
' and retrieve its text contents and paste them in to WordDocData textbox on
' the currently open form in Access
' Create an MS-Word Object:
Dim objDoc As Object
Set objDoc = CreateObject("Word.Application")
' Open the file selected in FileToOpen() and copy the contents to clipboard:
With objDoc
.Documents.Open strFile
.Visible = True
.Activate
.Selection.WholeStory
.Selection.Copy
End With
' Set the focus to the WordDocData textbox on the Access Form and paste clipboard:
Me.WordDocData.SetFocus
DoCmd.RunCommand acCmdPaste
Me.WordDocDataSrc = strFile
' Save record on the form:
If _
Me.Dirty _
Then
Me.Dirty = False
End If
' A bit hacky this bit. When you close MS-Word after copying a lot of data,
' you might get a message asking you if you if you want to keep the last item
' you copied. This essentially overwrites the clipboard that currently has
' the whole document stored, to just the first 5 characters, which should allow
' MS-Word to be closed here without a pop-up message to deal with:
With objDoc
.Selection.HomeKey Unit:=wdLine
.Selection.MoveRight Unit:=wdCharacter, Count:=5, Extend:=wdExtend
.Selection.Copy
.Documents.Close
.Quit
End With
Set objDoc = Nothing
End Sub
Your Command Button's On-click Event:
This subroutine should be run from your command button's on-click event. It essentially calls FileToOpen function and the GetWordContent subroutine in order for the user to select a MS-Word Document and then let the VBA copy and paste the formatted text from the MS-Word Document in to a rich text memo textbox on the open form in Access.
Note that this subroutine makes some assumptions, and refers to names of controls/tables/fields and whatnot that you might not have already setup. These assumptions are:
Your form's command button is called cmdGetWordData
Your Access database has a table called tblWordDump
Your form is bound to the table tblWordDump
tblWordDump has 2 memo text fields called WordDocDataSrc and WordDocData to store the imported file path/name and text contents respectively and both are added to your form
Private Sub cmdGetWordData_Click()
' This subroutine runs on your command button; it will call both the FileToOpen function and GetWordContent subroutine
' to retrieve the text contents of your chosen MS-Word Document.
' It will then store both the path the text contents of of your chosen MS-Word Document in 2 fields in a table in Access.
' NOTE: this code assumes that your Access database has:
' - a table called tblWordDump
' - a memo text field in this table called WordDocDataSrc to store the path of MS-Word file imported
' - a memo text field in this table called WordDocData with the TextFormat property set to "Rich Text",
' which will store the text and text formating of the MS-Word file imported
Dim strFile As String
Dim strWordContent As Variant
' Select file via File Dialogue
strFile = FileToOpen
' Conditionals when a file was or wasn't selected
If _
Len(strFile) > 0 _
Then
DoCmd.GoToRecord , , acNewRec
GetWordContent strFile
MsgBox "Import Successful", vbInformation Or vbOKOnly
Else
MsgBox "No File Selected", vbExclamation Or vbOKOnly
End If
End Sub
Here's an example Access file of this for you to poke about in.
I'm trying to write a Excel VBA code which allows me to automatically create and send a Lotus Notes Email. The problem I face is the difficulty to create a rich text Email, so I think it would be easier to open a draft email, with a marker text which will be replaced (for exameple PASTE EXCEL CELLS HERE) and then just:
.GotoField ("Body")
.FINDSTRING "PASTE EXCEL CELLS HERE"'
and replace.
Any help on how to open a certain draft email? Perhabs something as .CreateDocument property?
Thank you very much!
Others have proposed interesting concepts, but the most robust approach would be to use HTML in a MIME enitity that is mapped to the Body Rich Text item. Using NotesSession..Convertmime = False you can build the body as HTML and then send the message. Based on the post by Joseph Hoetzl here, the LotusScript equivalent is this:
Sub Initialize()
Dim s As New NotesSession
Dim db As NotesDatabase
Dim stime as Single
Dim alog As New NotesLog("debug")
Call alog.OpenAgentLog()
stime = Timer
On Error GoTo eh
Dim doc As NotesDocument
Dim body As NotesMIMEEntity
Dim header As NotesMIMEHeader
Dim stream As NotesStream
Dim child As NotesMIMEEntity
Dim sendTo As String
Dim subject As String
s.Convertmime = False
sendto = s.Effectiveusername
subject = "Demo Message"
Set db= s.Currentdatabase
Set doc=db.Createdocument()
Set stream = s.CreateStream
Set body = doc.CreateMIMEEntity
Set header = body.CreateHeader({MIME-Version})
Call header.SetHeaderVal("1.0")
Set header = body.CreateHeader("Content-Type")
Call header.SetHeaderValAndParams({multipart/alternative;boundary="=NextPart_="})
'Add the to field
Set header = body.CreateHeader("To")
Call header.SetHeaderVal(SendTo)
'Add Subject Line
Set header = body.CreateHeader("Subject")
Call header.SetHeaderVal(subject)
'Add the body of the message
Set child = body.CreateChildEntity
Call stream.WriteText("<h1>Demo HTML Message</h1>")
Call stream.WriteText(|<table colspacing="0" colpadding="0" border="none">|)
Call stream.WriteText(|<tr><td>cell 1.1</td><td>cell 1.2</td><td>cell 1.3</td></tr>|)
Call stream.WriteText(|<tr><td>cell 2.1</td><td>cell 2.2</td><td>cell 2.3</td></tr>|)
Call stream.WriteText(|<tr><td>cell 3.1</td><td>cell 3.2</td><td>cell 3.3</td></tr>|)
Call stream.WriteText(|</table>|)
Call stream.WriteText(|<div class="headerlogo">|)
Call stream.WriteText(|<!-- ...some more HTML -->|)
Call child.setContentFromText(stream, {text/html;charset="iso-8859-1"}, ENC_NONE)
Call stream.Truncate 'Not sure if I need this
Call stream.Close
Call doc.CloseMIMEEntities(True)
Call doc.replaceItemValue("Form", "Memo")
Call doc.Send(False, sendTo)
es:
Exit Sub
eh:
Dim emsg$
emsg = Error & " at " & Erl & " in " & s.Currentagent.name
Call alog.logError(Err, emsg)
MsgBox "ERROR: " & Err & ": " & emsg
Resume es
End Sub
All of this should convert fairly easily to VBA in Excel. You can, of course be as complex as you want with your HTML.
The word "draft" is probably inappropriate here, but then again so is the word "template". Both have specific meanings in Lotus Notes that aren't what you really want. Users can delete drafts, and templates are an entirely different thing. So let's just call it a boilerplate message.
I would recomemnd creating a special mail database (NSF file) on the Domino server, which will just serve as a repository for your boilerplate. You can create a folder in that mail database called "Boilerplates". Using Domino Designer you can modify that folder's design so that the Subject column is the first column in the view, and it is sorted.
Once you have that done and you have created some boilerplates and saved them in the folder, you can use VBA to do a NotesSession.getDatabase call, NotesDatabase.getView call (this is used for folders as well as views), and then use NotesView.getDocumentByKey() to retrieve a specific boilerplate by the Subject you have assigned to it. Note that you do not have to copy this document to the user's mail database in order to mail it.
What you want to do is non-trivial, but you mention a draft email so there may be a workaround.
In your mail settings you can specify a signature file which can be an external html file on disk. So modify the signature file, then create your new mail which will then populate the body field the way you want it.
For sample code, within the memo form there should be a button to specify what signature file to use. You can use that as the baseline.
Rich text is not that hard to work with, but you need to look at the Domino Designer help, especially the classes NotesRichTextItem and NotesRichTextStyle. You also need to understand the DOM (Domino Object Model). Then you can create your mail content programatically.
Otherwise I think Richard's solution is the best, that you have a separate database where you get the rich text snippets, and use the AppendRTItem method of the NotesRichText class to put it into your email.
Thank you all guys!
But I found exactly what I wanted, without having to recreate the whole Email everytime:
Sub EditSelectedMail()
Dim NSession As Object
Dim NDatabase As Object
Dim NUIWorkspace As Object
Dim NUIdoc As Object
Set NSession = CreateObject("Notes.NotesSession")
Set NUIWorkspace = CreateObject("Notes.NotesUIWorkspace")
Set NDatabase = NSession.GetDatabase("", "")
If Not NDatabase.IsOpen Then NDatabase.OPENMAIL
Set NUIdoc = NUIWorkspace.EDITDOCUMENT(True)
With NUIdoc
'Find the marker text in the Body item
.GotoField ("Body")
.FINDSTRING "**PASTE EXCEL CELLS HERE**"
'Copy Excel cells to clipboard
Sheets("Sheet1").Range("A1:E6").Copy
'Create a temporary Word Document
Set WordApp = CreateObject("Word.Application")
WordApp.Visible = False
WordApp.Documents.Add
'Paste into Word document and copy to clipboard
With WordApp.Selection
.PasteSpecial DataType:=10
'Enum WdPasteDataType: 10 = HTML; 2 = Text; 1 = RTF
.WholeStory
.Copy
End With
'Paste from clipboard (Word) to Lotus Notes document
.Paste
Application.CutCopyMode = False
'WordApp.Quit SaveChanges:=False
Set WordApp = Nothing
End With
End Sub
I just select my "template", copy it into a new message, and then run the macro.