Change built-in Document properties without opening - excel

I am attempting to run the below line of code in a sub. The purpose of the sub overall is to automatically create agendas for recurring meetings, and notify the relevant people.
'Values for example;
MtgDate = CDate("11/06/2020")
Agenda ="Z:\Business Manual\10000 Management\11000 Management\11000 Communications\Operations Meetings\11335 - OPS CCAR Performance Review Agenda 11.06.20.docx" 'NB it's a string
'and the problematic line:
Word.Application.Documents(Agenda).BuiltinDocumentProperties("Publish Date") = MtgDate
Two questions:
1) Can I assign a document property just like that without opening the document? (bear in mind this vba is running from an excel sheet where the data is stored)
2) Will word.application.documents accept the document name as a string, or does it have to be some other sort of object or something? I don't really understand Word VBA.
Attempts so far have only resulted in
runtime error 427 "remote server machine does not exist or is
unavailable"
or something about a bad file name.

Although Publish Date can be found under Insert > Quick Parts > Document Property it isn't actually a document property. It is a "built-in" CustomXML part, a node of CoverPageProperties, and can be addressed in VBA using the CustomXMLParts collection.
The CustomXML part is only added to the document once the mapped content control is inserted.
Below is the code I use.
As already pointed out for document properties the document must be open.
Public Sub WriteCoverPageProp(ByVal strNodeName As String, ByVal strValue As String, _
Optional ByRef docTarget As Document = Nothing)
'* Nodes: Abstract, CompanyAddress, CompanyEmail, CompanyFax, CompanyPhone, PublishDate
'* NOTE: If writing PublishDate set the content control to store just the date (default is date and time).
'* The date is stored in the xml as YYYY-MM-DD so must be written in this format.
'* The content control setting will determine how the date is displayed.
Dim cxpTarget As CustomXMLPart
Dim cxnTarget As CustomXMLNode
Dim strNamespace As String
If docTarget Is Nothing Then Set docTarget = ActiveDocument
strNodeName = "/ns0:CoverPageProperties[1]/ns0:" & strNodeName
strNamespace = "http://schemas.microsoft.com/office/2006/coverPageProps"
Set cxpTarget = docTarget.CustomXMLParts.SelectByNamespace(strNamespace).item(1)
Set cxnTarget = cxpTarget.SelectSingleNode(strNodeName)
cxnTarget.Text = strValue
Set cxnTarget = Nothing
Set cxpTarget = Nothing
End Sub

You cannot modify a document without opening it. In any event, "Publish Date" is not a Built-in Document Property; if it exists, it's a custom one.
Contrary to what you've been told, not all BuiltinDocumentProperties are read-only; some, like wdPropertyAuthor ("Author"), are read-write.

There are three main ways you could modify a Word document or "traditional" property (which are the ones you can access via .BuiltInDocumentProperties and .CustomProperties):
a. via the Object Model (as you are currently trying to do)
b. for a .docx, either unzipping the .docx, modifying the relevant XML part, and re-zipping the .docx.
c. For "traditional" properties, i.e. the things that you can access via .BuiltInDocumentProperties and .CustomDocumentProperties, in theory you can use a Microsoft .dll called dsofile.dll. But it hasn't been supported for a long time, won't work on Mac Word and the Microsoft download won't work on 64-bit Word. You'd also have to distribute and support it.
But in any case, "Publish Date" is not a traditional built-in property. It's probably, but not necessarily, a newer type of property called a "Cover Page Property". Those properties are in fact pretty much as "built-in" as the traditional properties but cannot be accessed via .BuiltInDocumentProperties.
To modify Cover Page properties, you can either use the object model or method (b) to access the Custom XML Part in which their data is stored. Method (c) is no help there.
Not sure where your error 427 is coming from, but I would guess from what you say that you are trying to see if you can modify the property in a single line, using the fullname of the document in an attempt to get Word to open it. No, you can't do that - you have to use GetObject/CreateObject/New to make a reference to an instance of Word (let's call it "wapp"), then (say)
Dim wdoc As Word.Document ' or As Object
Set wdoc = wapp.Documents.Open("the fullname of the document")
Then you can access its properties, e.g. for the read/write Title property you can do
wdoc.BuiltInDocumentProperties("Title") = "your new title"
wdoc.Save
If Publish Date is the Cover Page Property, once you have a reference to the Word Application and have ensured the document is open you can use code along the following lines:
Sub modPublishDate(theDoc As Word.Document, theDate As String)
' You need to format theDate - by default, Word expects an xsd:dateTime,
' e.g. 2020-06-11T00:00:00 if you only care about the date.
Const CPPUri As String = "http://schemas.microsoft.com/office/2006/coverPageProps"
Dim cxn As Office.CustomXMLNode
Dim cxps As Office.CustomXMLParts
Dim nsprefix As String
Set cxps = theDoc.CustomXMLParts.SelectByNamespace(CPPUri)
If cxps.Count > 0 Then
With cxps(1)
nsprefix = .NamespaceManager.LookupPrefix(CPPUri)
Set cxn = .SelectSingleNode(nsprefix & ":CoverPageProperties[1]/" & nsprefix & ":PublishDate[1]") '/PublishDate[1]")
If Not (cxn Is Nothing) Then
cxn.Text = theDate
Set cxn = Nothing
End If
End With
End If
Set cxps = Nothing
As for this, "Will word.application.documents accept the document name as a string", the answer is "yes", but Word has to have opened the document already. as mentioned above. Word can also accept an integer index into the .Documents collection and may accept just the name part of the FullName string.
Finally, if you do end up using a "traditional Custom Document Property", even after you have set the property and saved the document (approximately as above) you may find that the new property value has not actually saved! If so, that's down to an old error in Word where it won't save unless you have actually visited the Custom Document Property Dialog or have modified the document content in some way, e.g. adding a space at the end.

Related

VBA Sharepoint Check In File after Upload

Been using this code for a while to upload a file to SharePoint and noticed the other day that when the file is uploaded, it is checked out to myself automatically and have to go into SharePoint and manually check-in so that others can view the file. Any way to modify or add to my code below to auto check-in a file after it uploads? Totally stumped and any help would be greatly appreciated.
Sub SharePointUpload()
Dim WSN
Dim spAdd
Set WSN = CreateObject("WScript.Network")
spAdd = "https://mysharepoint/test"
WSN.mapnetworkdrive "N:", spAdd
ActiveWorkbook.Save
Dim SharepointAddress As String
Dim LocalAddress As String
Dim objNet As Object
Dim FS As Object
' Where you will enter Sharepoint location path
SharepointAddress = "\\mysharepoint\test"
' Where you will enter the local file path
LocalAddress = "C:\data\sample_file.xlsm"
Set objNet = CreateObject("WScript.Network")
Set FS = CreateObject("Scripting.FileSystemObject")
If FS.FileExists(LocalAddress) Then
FS.CopyFile LocalAddress, SharepointAddress
Else: MsgBox "File does not exist!"
End If
Set objNet = Nothing
Set FS = Nothing
WSN.removenetworkdrive "N:"
End Sub
I had the same issue with our SharePoint Document library even with option Require Check Out switched off - some documents (Word, Excel) were uploaded as Checked Out automatically (green arrow mark) whereas others were in fact Checked In from upload. Moreover when I wanted to Check In manually such documents, I got error message about missing mandatory field, although there was no field in the library set as mandatory.
The reason in my case was the field Title. This field is presented in library as default text field and it is also presented in Office files. I noticed that for Office files with empty field Title the document is automatically Checked Out during upload. When uploaded document contained some text in Title property, no Check Out was set. I also tried to use workflow to change the Title and then Check Out file (after creation), but it was not working - initial Check In was required to start the workflow. Manual change of document properties works but that's too tedious...
The first programmatic option to handle this issue could be to fill Title property of uploaded document when empty (i.e. add file name or any other text). Should work well with many languages. Example with Python:
from docx import Document
path = 'D:/myfile.docx'
document = Document(path)
document_property = document.core_properties
if not document_property.title:
document_property.title = 'Everything is Awesome'
document.save(path)
In VBA Title should accessible via Wb.BuiltinDocumentProperties("Title").
Another successful option for me was to find column Title in Columns of Document library and rename it to something else (I used Titlex as new name), then try to upload new document with empty Title - this time there was no automatic Check Out for the uploaded document. Then I could set the Titlex back to Title without returning the issue.

$Ref flag reappears even after deletion

We have an Xpage based Web mail-in application. Some emails are coming with $Ref field (response document). We wanted to convert them into normal document by removing $Ref field so that it will no longer a response one. We are able to delete the $ref field but it reappears again after saving the documents from web interface(xpage) and it makes all such documents again response document. This started happening recently and working well earlier. Can you please suggest what is the root cause? How to stop it?
Sub Initialize
On Error GoTo ErrorHandler
Dim s As New NotesSession
Dim db As NotesDatabase
Dim coll As NotesDocumentCollection
Dim doc As NotesDocument
Dim count As Long
Set db = s.Currentdatabase
Set coll = db.Unprocesseddocuments
MessageBox "Count : " & coll.Count
'Exit Sub
count = 1
Set doc = coll.Getfirstdocument()
While Not doc Is Nothing
If doc.Hasitem("$Ref") Then
Call doc.Removeitem("$Ref")
Call doc.Save(false, false)
End If
Print count
count = count + 1
Set doc = coll.Getnextdocument(doc)
Wend
Exit Sub
ErrorHandler:
MessageBox "Error " & Error & " at line " & Erl
Exit Sub
End Sub
Try changing your save method to Call doc.Save(true, false).
This will force the document save, and also prevent creating a response.
Also verify that you are running the agent on a server not accessable to active users. Also verify that you are only running your agent once, and that it isn't enabled on multible replicas.
Have you tried using If(doc.isResponse) Then instead of If doc.Hasitem("$Ref") Then
Note: I am looking in the java editor, but assume that the isResponse method is also in LS.
Wheather a document is a response is controlled by a property of the used form. As long as you do not assign another form that is a "Document" and not a "Response", you will most probably not be able to remove the "isresponse"- flag. As already told: most probably your xpage saves the "frontend" document after your backend manipulation and resets the state (form validation enabled in xpage properties). But without source code of the xpage one cannot tell.
Check the form name. Emails can use either the "Memo" form or the "Reply" form. Emails using the Reply form are response documents. You may also need to change the form to "Memo".

Getting values from profile document

I have some problem getting the value from the profile document. For creation, I followed these instructions:
Creating a profile form
1.Create a form with fields to hold the values you want to store in profile documents.
2.Choose Design - Form Properties and deselect "Include in Menu. and "Include in Search Builder"
3.Save the form.
4.Do not include the form in any view.
In the form I have only one field that is computed when composed (a number), with a computed value "0"
But with this code I can't retreive the field value:
Dim session as New NotesSession
Dim db as NotesDatabase
Dim doc as NotesDocument
Set db=session.CurrentDatabase
Set doc=db.GetProfileDocument("nameofprofiledoc")
dim number as integer
number=doc.fieldname(0)
Isprofile return true, but number is always "". For some reason it never gets the value
Perhaps it is because you haven't saved the document into the database yet. You can create an action button that let's you edit the document and then save it:
#Command( [EditProfile] ; formname );
Computed field formulas are not executed when you call GetProfileDocument. That's because GetProfileDocument is a "back-end" method. The form, with its various field definitions and formulas is used by the "front-end". (There's one exception: there's a ComputeWithForm method available in the back-end classes.) Anyhow. GetProfileDocument either loads a previously saved profile doc, or creates a new one that is essentially empty. #Ken's answer tells you how to manually create the profile document and save it so your code will find it. As an alternative, you can do the initialization in your code like this:
if ! doc.hasItem("fieldname") then
doc.replaceItemValue("fieldname",0)
end if
number=doc.fieldname(0)
That way you won't be dependent on someone manually creating the profile document. But if you are accessing the profile document from multiple places in code, and you can't predict which code might execute first, then you're probably going to want to create a script library with functions (or a class) and wrap GetProfileDocument in your own function (or method) to insure that all code paths will do the initialization properly.
As Richard mentions, when you call the method GetProfileDocument, the profile is created as a blank document, if not found in the database, (it will only have some initial fields that hold the profile name, conflict action, last editor)..
To initialize the profile, you might try to either:
1) Program the Form's QuerySave event to copy all items to the profile (then cancel the Save, to avoid storing the data in a regular document).
Sub Querysave(Source As Notesuidocument, Continue As Variant)
Dim db As NotesDatabase
Dim curdoc As NotesDocument
Dim profile As NotesDocument
Set curdoc = Source.Document
Set db = curodc.ParentDatabase
Set profile = db.GetProfileDocument(curdoc.Form(0))
Call curdoc.CopyAllItems(profile, True)
Call profile.Save(True, False)
Continue = False 'Don't allow the form to be saved to a regular document.
End Sub
Or:
2) Call GetProfileDocument to create a new profile or get an old one (you can tell the difference by checking the IsNewNote property. Then assign it a Form name, and compute it with this form.
In an Agent or an Action:
Sub InitializeProfile
Dim s As New NotesSession
Dim db As NotesDatabase
Dim profile As NotesDocument
Set db = s.CurrentDatabase
Set profile = db.GetProfileDocument("MyForm")
If profile.IsNewNote Then
Call profile.ReplaceItemValue("Form", "MyForm")
Call profile.ComputeWithForm(False, False) 'This will create the computed fields with their default values in the profile.
Call profile.Save(True, False)
End If
End Sub

Using multiple values field in Lotus Notes

I am trying to write a logging system for a form in Lotus Notes but I am at the part where I am not sure how I can append the information about the fields that are changed in the log fields. There are 3 fields that I use Log_Date (date), Log_User and Log_Actions (Text, allow multiple values).
I thought if I add comma to the log field it will create a new line when displaying the form but I am still getting a type mismatch on the case 2 line.
How can I append the new values to the log fields?
Sub Querysave(Source As Notesuidocument, Continue As Variant)
' Compare the values in the form after it is saved with its original values when the document is not a new document.
Dim doc As NotesDocument
Set doc = Source.Document
Dim session As New NotesSession
Dim user As String
user = session.CommonUserName
If newDoc Then
doc.Log_Date = Now()
doc.Log_User = user
doc.Log_Actions = "New document created."
Else
' Load fields value to the array
lastValues(0) = doc.QCR_No(0)
lastValues(1) = doc.QCR_Mobile_Item_No(0)
lastValues(2) = doc.QCR_Qty(0)
' Compared each value in the array to see if there is any difference
Dim i As Integer
For i = 0 To 2
If lastValues(i) <> originalValues(i) Then
Select Case i
Case 2 : doc.Log_Actions = doc.Log_Actions & "," & "Field QCR_Qty is changed"
End Select
End If
Next
End If
End Sub
doc.Log_Actions returns the notesitem. To access the value you need to use doc.Log_Actions(0)
In LotusScript back-end classes (e.g. NotesDocument, NotesItem), a multi-valued field is represented by an array with one value per element of the array. For setting the value of a field, doc.Log_Actions is shorthand (they call it 'extended syntax' in the Domino Designer help) for assigning the first (i.e., zero subscript) element of the array, but that does not work for getting the value. To get the first value, you have to use doc.Log_Actions(0). To get or set the second value, you have to use doc.Log_Actions(1).
So, your Case 2 code could look like this:
doc.Log_Actions(1) = "Field QCR_Qty is changed"
My guess, however, is that you really want to be able to continually append to the end of the list of values each time this code runs. You are also going to want your code to be robust and not blow up on you if (for any reason!) the Log_Actions item does not exist in the document. For that, you are going to want to do this:
dim actionsItem as NotesItem
if doc.hasItem("Log_Actions") then
set actionsItem = doc.getFirstItem("Log_Actions")
call actionsItem.AppendToTextList("Field QCR_Qty is changed")
end if
Or,
If (Not doc.HasItem("LogActions")) Then
doc.LogActions = "Field QCR_Qty is changed"
Else
doc.LogActions = ArrayAppend(doc.LogActions,"Field QCR_Qty is changed")
End If
This is equivalent to the NotesItem method by rhsatrhs, which you use is matter of preference.

Lotus Notes - Export emails to plain text file

I am setting up a Lotus Notes account to accept emails from a client, and automatically save each email as a plain text file to be processed by another application.
So, I'm trying to create my very first Agent in Lotus to automatically export the emails to text.
Is there a standard, best practices way to do this?
I've created a LotusScript Agent that pretty much works. However, there is a bug - once the Body of the memo exceeds 32K characters, it starts inserting extra CR/LF pairs.
I am using Lotus Notes 7.0.3.
Here is my script:
Sub Initialize
On Error Goto ErrorCleanup
Dim session As New NotesSession
Dim db As NotesDatabase
Dim doc As NotesDocument
Dim uniqueID As Variant
Dim curView As NotesView
Dim docCount As Integer
Dim notesInputFolder As String
Dim notesValidOutputFolder As String
Dim notesErrorOutputFolder As String
Dim outputFolder As String
Dim fileNum As Integer
Dim bodyRichText As NotesRichTextItem
Dim bodyUnformattedText As String
Dim subjectText As NotesItem
'''''''''''''''''''''''''''''''''''''''''''''''''''''''
'INPUT OUTPUT LOCATIONS
outputFolder = "\\PASCRIA\CignaDFS\CUser1\Home\mikebec\MyDocuments\"
notesInputFolder = "IBEmails"
notesValidOutputFolder = "IBEmailsDone"
notesErrorOutputFolder="IBEmailsError"
'''''''''''''''''''''''''''''''''''''''''''''''''''''''
Set db = session.CurrentDatabase
Set curview = db.GetView(notesInputFolder )
docCount = curview.EntryCount
Print "NUMBER OF DOCS " & docCount
fileNum = 1
While (docCount > 0)
'set current doc to
Set doc = curview.GetNthDocument(docCount)
Set bodyRichText = doc.GetFirstItem( "Body" )
bodyUnformattedText = bodyRichText.GetUnformattedText()
Set subjectText = doc.GetFirstItem("Subject")
If subjectText.Text = "LotusAgentTest" Then
uniqueID = Evaluate("#Unique")
Open "\\PASCRIA\CignaDFS\CUser1\Home\mikebec\MyDocuments\email_" & uniqueID(0) & ".txt" For Output As fileNum
Print #fileNum, "Subject:" & subjectText.Text
Print #fileNum, "Date:" & Now
Print #fileNum, bodyUnformattedText
Close fileNum
fileNum = fileNum + 1
Call doc.PutInFolder(notesValidOutputFolder)
Call doc.RemoveFromFolder(notesInputFolder)
End If
doccount = doccount-1
Wend
Exit Sub
ErrorCleanup:
Call sendErrorEmail(db,doc.GetItemValue("From")(0))
Call doc.PutInFolder(notesErrorOutputFolder)
Call doc.RemoveFromFolder(notesInputFolder)
End Sub
Update
Apparently the 32KB issue isn't consistent - so far, it's just one document that starts getting extra carriage returns after 32K.
With regards the 32Kb thing, instead of this:
Set bodyRichText = doc.GetFirstItem( "Body" )
... you might want to consider iterating all "Body" fields in the email document. When dealing with large amounts of rich text, Domino "chunks" said content into multiple rich text fields. Check some documents you're processing: you may well see multiple instances of the "Body" field when you look at document properties.
I'm not sure what is causing the 32K bug, but I know there are lots of limitations in the order of 32K or 64K within Lotus Notes, so perhaps you're running into one of those. I can't imagine what would add extra CR/LFs. Perhaps you could try using the GetFormattedText method on the NotesRichTextItem class and see if it fares better?
It's more complicated, but you might also be able to use the NotesRichTextNavigator class to iterate through all the paragraphs in the memo, outputting them one at a time. Breaking up the output that way might eliminate the CR/LF problem.
Lastly I always suggest Midas' LSX for dealing with rich text in Lotus Notes. They sell an add-on that gives you much more control over rich text fields.
As for best practices, one that comes to mind when I read your code is the looping construct. It is more efficient to get the first document in a view, process it, and then get the next doc and check whether it is equal to Nothing. That sets the loop to run through the view in index order, and eliminates the need to search through the index to find the Nth document each time. It also saves you from maintaining a counter. The gist is as follows:
Set doc = curview.GetFirstDocument()
While Not (doc Is Nothing)
'Do processing here...
Set doc = curview.GetNextDocument(doc)
Wend
The external eMail most likely comes in as MIME. So you could check the document.hasMime and then use the mime classes to get to the content. Then you don't have a 64k limit. Samples are in the help - or reply if you want code.

Resources