How to check in Python if Macro execution is allow?

Is there a way to use the API and python to test if Macro execution is allowed for a spreadsheet?

I have an extension that runs in python but I don’t want the python code to continue executing if macros are not enabled for the document. Is short I want to wait for the user to click Allow Macro Execution before continuing to execute python code.

Even better I could invoke the Allow Macros dialog box.

1 Like

After more digging I found that documents have a getArgs() that return a sequence of PropertyValues

from ooodev.utils.props import Props
doc_args = self.document.getArgs()

# convert doc_args in to a Class with attributes
# that match the doc_args property values.
args_dic = Props.props_to_dot_dict(doc_args)
print(args_dic.MacroExecutionMode)

For documents with macros enabled prints 4


For documents with macros disabled prints 0


Can anyone confirm this is correct?

1 Like
Sub ShowMacroSecurityLevel()
  Dim oConfig As Object, key As String
  oConfig=Registry_KeyContent("org.openoffice.Office.Common/Security/Scripting", False)
  key="MacroSecurityLevel"
  Msgbox key & ": " & oConfig.getPropertyValue(key)
End Sub


' lang:en
' Returns LO configuration parameters.
' - key        context. If it does not start with "org.", Then "org.openoffice." is added to the left.
' - bforUpdate True: parameters can be updated.
'
' The function is the same as the GetRegistryKeyContent function from the application Tools library.
Function Registry_KeyContent(ByVal key as string, Optional Byval bforUpdate as Boolean)
  Dim oConfigProvider as Object
  Dim aNodePath(0) as new com.sun.star.beans.PropertyValue
  If IsMissing(bforUpdate) Then bforUpdate=False
  If Instr(1, lcase(key), "org.")<>1 Then key="org.openoffice." & key
    
  oConfigProvider = GetDefaultContext.getValueByName("/singletons/com.sun.star.configuration.theDefaultProvider")
  aNodePath(0).Name = "nodepath" : aNodePath(0).Value = key
  Registry_KeyContent=oConfigProvider.createInstanceWithArguments("com.sun.star.configuration.Configuration" & _
                      IIf(bforUpdate, "Update", "") & "Access", aNodePath()) 
End Function
2 Likes

Requirements


I needed a way to detect at the document level if macros have been enabled. The VBA and Python examples below show the Security Level set for LibreOffice, usually via the `Tools -> Options -> LibreOffice -> Security -> Macro Security...`

So, the code examples below can give that value.


Lets assume the security level is set to Medium which means the user will be prompted to enable macros when a document is opened that uses macros.
That is the use case I am wanting to detect.


My testing, On Ubuntu 24.04, LibreOffice 24.08, showed accessing the document.getArgs() and the MacroExecutionMode property is consistent no matter the security level set in LibreOffice Settings. However, just because my testing showed it is consistent does not mean this is the most correct way to accomplish this. So far it is best approach I know.

VBA

I asked Copilot to explain:


Certainly! Let’s break down and explain the provided LibreOffice Basic macro step by step.

Overview

The macro consists of two parts:

  1. ShowMacroSecurityLevel Subroutine: Retrieves and displays the current macro security level setting in LibreOffice.
  2. Registry_KeyContent Function: Accesses LibreOffice’s configuration registry to retrieve configuration nodes.

1. ShowMacroSecurityLevel Subroutine

Sub ShowMacroSecurityLevel()
  Dim oConfig As Object, key As String
  oConfig = Registry_KeyContent("org.openoffice.Office.Common/Security/Scripting", False)
  key = "MacroSecurityLevel"
  Msgbox key & ": " & oConfig.getPropertyValue(key)
End Sub

Explanation:

  • Purpose: Retrieves the value of the MacroSecurityLevel setting from LibreOffice’s configuration and displays it in a message box.

  • Steps:

    • Declare Variables:
      • oConfig as an Object to hold the configuration node.
      • key as a String to specify the configuration property name.
    • Access Configuration Node:
      • Calls Registry_KeyContent with the node path "org.openoffice.Office.Common/Security/Scripting" to get the configuration object.
    • Set Key and Display Value:
      • Assigns "MacroSecurityLevel" to key.
      • Uses oConfig.getPropertyValue(key) to retrieve the value.
      • Displays the result using MsgBox.

2. Registry_KeyContent Function

Function Registry_KeyContent(ByVal key As String, Optional ByVal bforUpdate As Boolean)
  Dim oConfigProvider As Object
  Dim aNodePath(0) As New com.sun.star.beans.PropertyValue
  If IsMissing(bforUpdate) Then bforUpdate = False
  If InStr(1, LCase(key), "org.") <> 1 Then key = "org.openoffice." & key
  
  oConfigProvider = GetDefaultContext.getValueByName("/singletons/com.sun.star.configuration.theDefaultProvider")
  aNodePath(0).Name = "nodepath" : aNodePath(0).Value = key
  Registry_KeyContent = oConfigProvider.createInstanceWithArguments("com.sun.star.configuration.Configuration" & _
                          IIf(bforUpdate, "Update", "") & "Access", aNodePath()) 
End Function

Explanation:

  • Purpose: Provides access to a specified configuration node in LibreOffice.
  • Parameters:
    • key: The configuration node path.
    • bforUpdate (Optional): Determines if the configuration can be updated (True) or is read-only (False).
  • Steps:
    • Initialize Variables:
      • oConfigProvider to access the configuration provider.
      • aNodePath array to hold the node path property.
    • Handle Optional Parameter:
      • Sets bforUpdate to False if not provided.
    • Adjust Key Path:
      • Ensures key starts with "org."; if not, prepends "org.openoffice.".
    • Get Configuration Provider:
      • Obtains the configuration provider singleton from the default context.
    • Set Node Path Property:
      • Assigns "nodepath" and the adjusted key to aNodePath(0).
    • Create Configuration Access Instance:
      • Builds the service name based on bforUpdate (e.g., "com.sun.star.configuration.ConfigurationAccess").
      • Calls createInstanceWithArguments to get the configuration access object.
      • Returns the configuration object.

Understanding the Code in Context


  • ShowMacroSecurityLevel:
    • Displays the current macro security level by accessing the appropriate configuration node.
    • The security level indicates how LibreOffice handles macros:
      • 0: Low (not recommended).
      • 1: Medium (confirmation required).
      • 2: High (only signed macros).
      • 3: Very High (only from trusted sources).
      • 4: Disable all macros.
  • Registry_KeyContent:
    • A utility function to access configuration settings.
    • Can be used to read or write settings based on the bforUpdate parameter.
    • Adjusts the provided key to ensure it points to the correct configuration node.

Summary


  • The macro retrieves and displays the macro security level setting from LibreOffice’s configuration.
  • It uses UNO (Universal Network Objects) API to access configuration nodes.
  • By understanding and possibly modifying the Registry_KeyContent function, you can access other configuration settings as needed.

Python


Here is a similar function in Python.
from __future__ import annotations
import uno
from com.sun.star.beans import PropertyValue
from ooodev.loader import Lo
from ooodev.calc import CalcDoc
from ooodev.conn.cache import Cache


def are_macros_enabled(ctx=None):
    if ctx is None:
        # Get the component context
        ctx = uno.getComponentContext()
    smgr = ctx.ServiceManager

    # Access the configuration provider
    config_provider = smgr.createInstanceWithContext(
        "com.sun.star.configuration.ConfigurationProvider", ctx
    )

    # Specify the node path for macro security settings
    # node_path = "/org.openoffice.Office.Common/Save/Document"
    node_path = "org.openoffice.Office.Common/Security/Scripting"
    node = PropertyValue()
    node.Name = "nodepath"
    node.Value = node_path

    # Access the configuration node
    config_access = config_provider.createInstanceWithArguments(
        "com.sun.star.configuration.ConfigurationAccess", (node,)
    )

    # Get the macro security level
    macro_security_level = config_access.getPropertyValue("MacroSecurityLevel")

    # Determine if macros are enabled
    # If security level is 5, macros are disabled
    # see: https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1document_1_1MacroExecMode.html
    # macros_enabled needs more testing.
    macros_enabled = macro_security_level != 5
    return macros_enabled, macro_security_level


def main():
    with Lo.Loader(
        connector=Lo.ConnectSocket(),
        cache_obj=Cache(profile_path="", no_shared_ext=True),
    ) as loader:
        doc = None
        try:
            doc = CalcDoc.create_doc(loader=loader, visible=True)

            enabled, security_level = are_macros_enabled()

            if enabled:
                print(f"Macros are enabled. Security level: {security_level}")
            else:
                print("Macro execution is disabled.")

            print("Done")

        finally:
            if doc is not None:
                doc.close()


if __name__ == "__main__":
    main()
2 Likes