The module I created is a quick hack; no error checking, so you might destroy your valuable documents. Use at your own risk, have a reliable backup at hand, and never run this with root rights!!
Tl;dr:
- Download the module content here and save it somewhere.
- Start
Libreoffice
and go to the macro organisation via tools->macros->organize macros->libreoffice basic.
- Make sure from the macro listing on the left there is a library named "Tools" in container "LibreOffice Macros" (the macros in there are needed)
- Select "Organizer" on the right of the dialog
- Open the container "My Macros", select "Standard", and on the right select "New"
- Give the module the name "saveWithPassword"
- With the newly created module highlighted, select "Edit". This will open the IDE with the new module displayed
- Make sure you did select the correct module, then delete the content of the editor window.
- Open the downloaded module file with any GUI editor (e.g.
gedit
),select all and then cut&paste the complete sourcecode into the LibreOffice editor window. Save and Close and you're done.
- If you are familiar with programming and debugging, you can open a .odt file, open the macro IDE (like above), and step through the macro, look at the content of variables & the like.
Now, how to use the macros?
Suppose there is a list of .odt files that should be protected with the same password. The protected files shall be saved in a different target folder (has to exist) to keep the originals unchanged. Then you call
$ lowriter --invisible <list of .odt files> macro:///standard.saveWithPasswd.saveWithPasswd
(supposed you did create the module as suggested). After loading the files, the macro will be started and you will be asked for the target directory and the password. The documents will then be saved in the target directory, protected with the password supplied.
If you do not want to have all the documents opened in windows, there is a second (silent) method:
Make sure there is no instance of LibreOffice
running. Then run
$ export password=<the password to use>
$ export target=<full path to the target directory>
$ lowriter --headless <list of .odt files> macro:///standard.saveWithPasswd.saveWithPasswd
Now for the details
The main function is to write the file, protected by password. There are different ways to do that; if you just record what will be done interactively you end up with something like
document = ThisComponent.CurrentController.Frame
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
dim args1(2) as new com.sun.star.beans.PropertyValue
args1(0).Name = "URL"
args1(0).Value = "file:///home/user/Untitled%201.odt"
args1(1).Name = "FilterName"
args1(1).Value = "writer8"
args1(2).Name = "EncryptionData"
args1(2).Value = Array(Array("PackageSHA256UTF8EncryptionKey",Array(-14,-81,-47,-54,-53,84,65,-91,-26,90,122,70,10,95,-104,-104,-73,-71,-117,8,-86,99,35,-94,-27,60,-117,-102,-106,-122,-51,-122)),Array("PackageSHA1UTF8EncryptionKey",Array(24,97,84,113,43,45,95,103,-111,-40,91,-102,9,-121,-71,-113,-94,49,119,-100)),Array("PackageSHA1MS1252EncryptionKey",Array(24,97,84,113,43,45,95,103,-111,-40,91,-102,9,-121,-71,-113,-94,49,119,-100)))
dispatcher.executeDispatch(document, ".uno:SaveAs", "", 0, args1())
what is far too complicated.
In [2] 5.8.3 I found the much easier solution:
Dim args(0) As New com.sun.star.beans.PropertyValue
Dim sURL$
args(0).Name ="Password"
args(0).Value = "test"
sURL=ConvertToURL("/andrew0/home/andy/test.odt")
ThisComponent.storeToURL(sURL, args())
The most part of the work to do now was to
- pass the password and target filename/directory to the macro
- iterate over the open documents
- work with environment variables and dialogues
- find and use existing library functions
Some parts of the module I will describe more deeply (I think the rest is trivial):
GlobalScope.BasicLibraries.loadLibrary("Tools")
This loads the global LibreOffice
library "Tools". Some of the macros in there are used for handling of strings etc.
oComponents = StarDesktop.getComponents()
oDocs = oComponents.createEnumeration()
Do While oDocs.hasMoreElements()
oDoc = oDocs.nextElement()
.
.
Loop
This iterates over the currently open documents/frames. As for testing and debugging there was also the IDE open, I had to test if the frame really contained a valid document:
sUrl = oDoc.getUrl()
If sUrl <> "" Then
Now if the document has an URL defined (don't test here if it really exists...) a new URL is created and the document saved there with password:
sName = FileNameoutofPath(sUrl)
sName = myTargetDir & "/" & sName
sUrl = ConvertToURL(sName)
aArgs(0).Name = "Password"
aArgs(0).Value = myPassword
oDoc.storeToURL(sURL, aArgs())
oDoc.close(true)
Here the functions of the "Tools" library are used. Especially when calling lowriter
with option --headless
, the document must be closed at the end, as otherwise lowriter
does not terminate.
I could not manage to pass "password" and "target directory" as parameters to the module, so I use the two ways: As shell environment variables, as this can be fully automated, and as dialogue input.
Getting the password is just trivial using the "Environ" function and the default "InputBox". If you want to have the password hidden while inputting, you have to create your own dialog.
Picking the target folder is a bit more tricky, as this is no simple runtime function:
oFileDialog = CreateUnoService("com.sun.star.ui.dialogs.FolderPicker")
oUcb = createUnoService("com.sun.star.ucb.SimpleFileAccess")
InitPath = GetPathSettings("Work")
If oUcb.Exists(InitPath) Then
oFileDialog.SetDisplayDirectory(InitPath)
End If
If you decide to have a static "InitPath" (the path where the "FolderPicker" starts when opened), you can define that and cut everything from oUcb =
to End If
, except oFileDialog.SetDisplayDirectory
. When run from the command line you also might set InitPath = Environ("PWD")
. For now the start is the folder you have defined as your "My Documents" folder in the LibreOffice
settings.
oFileDialog.SetTitle(sTitle)
iAccept = oFileDialog.Execute()
If iAccept = 1 Then
sPath = oFileDialog.Directory
getFolderName = sPath
End If
I think this is self-explaining. But beware, there is no error checking here - if you cancel the file dialog, the TargetPath will not be defined. This should lead to a complete break of the macro, or you have to define a while
loop instead of the If
in askForTarget
:
Do While myTargetDir = ""
myTargetDir = getFolderName("Please select output directory")
Loop
You got the idea...
There are lots of modifications I could think of. At first, do error checking to be sure not to overwrite the original files (e.g. test if TargetDir
is empty) or do other harm. Or, create a text file with the original filesnames, the target and the password, and pass that to the macro to be done sequentially (you got the idea?). Or you have all the original documents in one folder, pass that folder path to the macro, and then iterate the directory listing (there are examples as well).
During research, I found (and used) some good documentation with lots of examples:
[1] OpenOffice Macros Explained by Andrew Pitonyak (also available as german version)
[2] Example macros by the same author: Examples described and Untested macros
[3] LibreOffice API documentation
and questions/answers like https://ask.libreoffice.org/en/question/29997/how-to-run-a-macro-on-multiple-files-batch/
Finally, thank you for your question and your patience. It was a lot of fun to come back to macro programming, and I have refreshed some knowledge and learned many new things.
soffice --headless <filename> <macroname>