If you are an administrator or developer navigating the backend of Dassault Systèmes’ 3DEXPERIENCE or Enovia environments, you already know that TCL and MQL (Matrix Query Language) is the backbone of Enovia/3DEXPERIENCE administration. It is the command-line interface where the real heavy lifting happens. Administrators frequently run .tcl scripts to automate bulk updates, migrate legacy data, correct lifecycle states, and patch metadata.
However, as your PLM environment scales, maintaining these scripts can become a nightmare. You might find your directory cluttered with dozens of nearly identical scripts—one for parts, one for documents, one for drawings—all because of a fundamental quirk in how MQL handles script execution. However, many hit a roadblock when trying to pass dynamic arguments into these scripts because standard eval {} blocks do not inherently accept them.
In this comprehensive, deep dive, we are going to dismantle this architectural bottleneck. We will explore exactly why the default scripting approach fails you, and we will break down three distinct, real-world strategies to pass arguments to your TCL scripts dynamically. By mastering these techniques, you will transition from writing rigid, single-use scripts to developing modular, reusable administrative tools.
The Core Problem: Why the Standard eval {} Block Fails
To understand the solution, we must first understand the architecture. MQL is fundamentally a database query language designed to interact with the Matrix kernel. To allow for programmatic logic (loops, if/else statements, variables), the Matrix kernel embeds a TCL (Tool Command Language) interpreter.
When a developer first learns to write a script for Enovia, they are usually taught the standard boilerplate:
Tcl
tcl;
eval {
# Your MQL commands and TCL logic here
puts "Script is running..."
}
This eval {} block simply tells the interpreter: “Take everything inside these brackets and evaluate it as a script.” The problem? The eval command in this context does not have a mechanism to catch external parameters. If you try to call this script from the MQL command line using something like source myScript.tcl "VPMReference" "Released", those arguments evaporate. The script executes, but it has no idea you tried to pass it data.
Real-World Scenario: You need to run a cleanup script that changes the maturity state of specific object types, but you want to reuse the exact same script for VPMReference one day and Drawing the next, without hardcoding the types.
If you rely on the standard eval {} block, you are forced to create two separate files: cleanup_VPMReference.tcl and cleanup_Drawing.tcl. When you manage a complex PLM schema with hundreds of types, this hardcoding approach leads to massive technical debt.
Let’s look at the solutions to break out of this limitation.
Solution 1: The Procedure (proc) Wrapper Approach
The cleanest and most architecturally sound way to handle parameters in an MQL TCL script is to abandon the raw eval block entirely. The first is wrapping your logic in a proc (procedure) instead of a raw eval block.
By defining a procedure, you explicitly declare that your block of code expects input parameters.
Implementation Guide
Instead of evaluating anonymous code, you define a named function (e.g., main) and declare its arguments. Once the procedure is loaded into the interpreter’s memory, you immediately invoke it with your desired parameters.
Tcl
# 1. Open the TCL interpreter
tcl;
# 2. Define the procedure and its expected arguments
proc main {objType targetState} {
puts "--------------------------------------------------"
puts "Executing Bulk Update Script"
puts "Target Object Type : $objType"
puts "Target State : $targetState"
puts "--------------------------------------------------"
# 3. Construct the MQL query using the provided arguments
# We use 'mql temp query bus' to find the objects
set mqlCommand "temp query bus '$objType' * * select id dump |"
# 4. Execute the query and catch any errors
if { [catch {set queryResult [eval mql $mqlCommand]} errMsg] } {
puts "ERROR: Failed to execute query. Details: $errMsg"
return -1
}
# 5. Process the results
set objectList [split $queryResult \n]
set count 0
foreach row $objectList {
if {$row != ""} {
set data [split $row |]
set objId [lindex $data 3]
# 6. Execute the promotion logic
puts "Promoting Object ID: $objId to state $targetState"
catch { mql promote bus $objId }
incr count
}
}
puts "Successfully processed $count objects."
}
# 7. Invoke the procedure (Arguments must be passed here or from the caller)
# main "VPMReference" "RELEASED"
Real-World Implementation Scenario
Imagine you are writing a utility that other administrators will use. You save the above script as PromoteObjects.tcl (leaving step 7 commented out).
An administrator opens their MQL console and types:
Code snippet
MQL> tcl;
MQL> source PromoteObjects.tcl
MQL> main "Drawing" "Approved"
Why this works beautifully:
- Modularity: The script acts as a loaded library. You can source it once and run the
mainprocedure multiple times with different arguments in a single session. - Scope Protection: Variables defined inside a
procare strictly local. They will not bleed into your global MQL session and overwrite other variables, which is a common hazard when using rawevalblocks.
Solution 2: The Environment Variable (env) Strategy
While the proc approach is elegant for interactive sessions, it can be slightly cumbersome if you are trying to trigger MQL scripts automatically from external systems, like a Windows Batch file (.bat), a Linux Bash script (.sh), or a Jenkins CI/CD pipeline.
Alternatively, developers can use set env var to establish variables before triggering the script, and then use get env var internally to retrieve them safely.
Enovia MQL maintains its own internal environment variables. By setting these variables immediately before sourcing the TCL script, the script can reach out to the environment, pull the values, and execute.
Implementation Guide
First, let’s write the TCL script (UpdateDescription.tcl) that expects environment variables to be present:
Tcl
tcl;
eval {
puts "Starting Description Update Utility..."
# 1. Safely retrieve the environment variables
# We use catch to prevent the script from crashing if the user forgot to set them
if { [catch {set myType [mql get env TARGET_TYPE]} errMsg] } {
puts "CRITICAL ERROR: Environment variable TARGET_TYPE is not set."
exit
}
if { [catch {set myDesc [mql get env NEW_DESCRIPTION]} errMsg] } {
puts "CRITICAL ERROR: Environment variable NEW_DESCRIPTION is not set."
exit
}
puts "Processing Type: $myType"
puts "Applying Description: $myDesc"
# 2. Execute the business logic
set queryResult [mql temp query bus "$myType" * * select id dump |]
set records [split $queryResult \n]
foreach record $records {
if {$record != ""} {
set objId [lindex [split $record |] 3]
# Update the object description
mql mod bus $objId description "$myDesc"
puts "Updated Object ID: $objId"
}
}
# 3. Best Practice: Cleanup environment variables after use
mql set env TARGET_TYPE ""
mql set env NEW_DESCRIPTION ""
puts "Utility execution complete."
}
Real-World Implementation Scenario
You are performing a massive weekend data migration. You have an external shell script orchestrating the MQL calls. Your external wrapper script would look like this:
migration_wrapper.sh:
Bash
#!/bin/bash
echo "Triggering Enovia Migration..."
# Call MQL, set the environment variables, and source the script in one continuous command execution
mql -c "
set env TARGET_TYPE 'CAD Model';
set env NEW_DESCRIPTION 'Migrated from Legacy System 2026';
source 'UpdateDescription.tcl';
"
echo "Migration batch complete."
Why this works beautifully:
- Automation Friendly: It is incredibly easy to pass dynamic data from OS-level scripts (like a Bash loop reading from a CSV file) directly into MQL.
- No Code Modification: The TCL script remains completely static. The behavior is driven entirely by the external environment state.
Solution 3: The Native TCL $argv List Method
There is a third, slightly more advanced method that leverages native TCL command-line argument handling. When you run a TCL script natively, any arguments passed after the script name are automatically stored in a special global list variable called $argv. The total count of these arguments is stored in $argc.
While standard eval {} blocks in MQL struggle with this, running a script using the MQL Run Prog command or executing a pure .tcl program through the Matrix interpreter allows you to access these variables.
Implementation Guide
Let’s create a script named GenerateReport.tcl designed to catch native arguments:
Tcl
# Notice we do NOT start with 'tcl;' and 'eval {'
# This script must be executed using the Matrix program runner, not simply sourced.
# 1. Check if the correct number of arguments was passed
if { $argc != 2 } {
puts "USAGE ERROR: GenerateReport.tcl requires exactly 2 arguments."
puts "Example: Run Prog GenerateReport.tcl 'Part' 'Obsolete'"
exit 1
}
# 2. Extract arguments from the $argv list
# lindex is used to grab items from a TCL list based on their index (starting at 0)
set typeArg [lindex $argv 0]
set stateArg [lindex $argv 1]
puts "Generating Audit Report for $typeArg in state $stateArg..."
# 3. Execute logic
set results [mql temp query bus "$typeArg" * * current "$stateArg" select name revision owner dump ,]
# 4. Output to a file
set fileId [open "AuditReport_${typeArg}_${stateArg}.csv" "w"]
puts $fileId "Type,Name,Revision,Owner"
foreach line [split $results \n] {
if {$line != ""} {
puts $fileId $line
}
}
close $fileId
puts "Report successfully generated to the server directory."
Real-World Implementation Scenario
This method is incredibly useful when integrating with JPOs (Java Program Objects) or when you have registered the TCL script as an actual “Program” object within the 3DEXPERIENCE database.
To execute this from the MQL command line, you would use:
Code snippet
MQL> insert prog GenerateReport.tcl;
MQL> compile prog GenerateReport.tcl;
MQL> execute prog GenerateReport.tcl "Part" "Obsolete";
Why this works beautifully:
- Strict Validation: Using
$argcallows you to strictly enforce how many arguments the user must provide before the script attempts to run, preventing catastrophic partial-executions. - Enterprise Standard: Registering scripts as actual
Programobjects in the database (rather than just sourcing flat files from a server directory) is a much more secure and trackable way to manage administrative utilities in a production environment.
Best Practices for Robust MQL/TCL Parameterization
Transitioning from hardcoded scripts to parameterized utilities is a major step forward, but it introduces new risks. When a script can accept dynamic input, it can also accept bad dynamic input. To ensure your PLM environment remains stable, you must implement the following best practices:
1. Always Validate Inputs (Sanitization)
Never blindly trust the arguments passed into your script. If your script executes an mql delete bus command based on an input, a simple typo could wipe out the wrong data. Always validate the input against a known list of acceptable values.
Tcl
proc deleteSpecificType {objType} {
# Define an allowed list
set allowedTypes {"DummyPart" "TestDocument" "TemporaryBOM"}
# Check if the input exists in the allowed list
if {[lsearch -exact $allowedTypes $objType] == -1} {
puts "SECURITY ERROR: Deletion of type '$objType' is strictly prohibited."
return
}
puts "Input validated. Proceeding with deletion..."
# Execute deletion logic...
}
2. Provide Fallback Defaults
Sometimes, an argument should be optional. If the user doesn’t provide it, the script should gracefully fall back to a safe default value.
Tcl
proc generateExport {objType {exportFormat "CSV"}} {
# The variable exportFormat has a default value of "CSV".
# If the user only passes the objType, exportFormat defaults to CSV.
# If the user passes "Part" and "XML", exportFormat becomes XML.
puts "Exporting $objType to format $exportFormat"
}
3. Beware of Quoting Hell
MQL and TCL handle quotes differently. When passing arguments that contain spaces (e.g., a description like “Update for Q3 2026”), you must wrap the argument in quotes when calling the procedure. Furthermore, when injecting that variable into an MQL command, wrap the variable in single quotes to protect it from the Matrix parser.
Tcl
# BAD: If $myDesc contains spaces, MQL will throw a syntax error
mql mod bus $objId description $myDesc
# GOOD: Protect the string with single quotes
mql mod bus $objId description '$myDesc'
4. Clean Up Your Environment (env) Variables
If you utilize Solution 2 (Environment Variables), remember that set env creates a variable that persists for the duration of the current MQL session. If you run one script that sets TARGET_TYPE, and then run a different script an hour later in the same session, that second script might accidentally read the old TARGET_TYPE value.
Always execute mql set env VAR_NAME "" at the end of your script to flush the data and prevent memory bleed.
Conclusion
The evolution of a PLM Administrator often mirrors the evolution of their scripts. Writing a quick, hardcoded eval {} block to fix a one-off issue is a rite of passage. But as your responsibilities grow, your tools must grow with them.
By wrapping your TCL logic in proc statements, orchestrating inputs via set env var, or leveraging native $argv lists, you transform static scripts into dynamic, reusable utilities. You reduce technical debt, drastically decrease the likelihood of human error during late-night data migrations, and build a robust toolkit that can handle whatever your 3DEXPERIENCE environment throws at you.
I have fleshed out the technical context, added real-world enterprise scenarios, and included the code snippets and configurations discussed by the community to make these blog posts highly actionable for your readers. Start refactoring your administrative toolkit today, and keep exploring the deeper mechanics of the Matrix query language.
